diff --git a/.gitignore b/.gitignore index ef7a9f241f2..ab409099416 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,11 @@ results CommonAssemblyInfo.cs lib/sqlite/System.Data.SQLite.dll *.orig +*.zip Samples/DataBus/storage packages PrecompiledWeb +tempstorage core-only Release Artifacts @@ -82,4 +84,8 @@ _ReSharper*/ src/scaffolding.config # Approval tests temp file -*.received.* \ No newline at end of file +*.received.* + +# JetBrains Rider +.idea/ +*.sln.iml \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000000..5c932e69584 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,7 @@ +assembly-versioning-scheme: Major +next-version: 6.0 +branches: + develop: + tag: alpha + releases?[/-]: + tag: rc \ No newline at end of file diff --git a/GitVersionConfig.yaml b/GitVersionConfig.yaml deleted file mode 100644 index 10b5dc97c04..00000000000 --- a/GitVersionConfig.yaml +++ /dev/null @@ -1 +0,0 @@ -assembly-versioning-scheme: Major \ No newline at end of file diff --git a/IntegrationTests/GenericHost/GenericHost.sln b/IntegrationTests/GenericHost/GenericHost.sln deleted file mode 100644 index ee07a49583f..00000000000 --- a/IntegrationTests/GenericHost/GenericHost.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{15E0163A-315E-4473-9BF3-DD9EA128EBD7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggingWithConfigurableThreshold", "LoggingWithConfigurableThreshold\LoggingWithConfigurableThreshold.csproj", "{3BF3849F-A0F5-4B9A-BB8F-12D4219F7787}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {15E0163A-315E-4473-9BF3-DD9EA128EBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15E0163A-315E-4473-9BF3-DD9EA128EBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15E0163A-315E-4473-9BF3-DD9EA128EBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15E0163A-315E-4473-9BF3-DD9EA128EBD7}.Release|Any CPU.Build.0 = Release|Any CPU - {3BF3849F-A0F5-4B9A-BB8F-12D4219F7787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3BF3849F-A0F5-4B9A-BB8F-12D4219F7787}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3BF3849F-A0F5-4B9A-BB8F-12D4219F7787}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3BF3849F-A0F5-4B9A-BB8F-12D4219F7787}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/IntegrationTests/GenericHost/Logging/App.config b/IntegrationTests/GenericHost/Logging/App.config deleted file mode 100644 index 777836e80b2..00000000000 --- a/IntegrationTests/GenericHost/Logging/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - -
-
- - - - - - diff --git a/IntegrationTests/GenericHost/Logging/Endpoint.cs b/IntegrationTests/GenericHost/Logging/Endpoint.cs deleted file mode 100644 index c2749d91be4..00000000000 --- a/IntegrationTests/GenericHost/Logging/Endpoint.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using NServiceBus; - -namespace Logging -{ - /* In this sample, we want our own production logging while leaving the regular NServiceBus - configuration of the endpoint so we specify "Logging.MyProductionProfile" on the command line. - */ - - public class Endpoint : IConfigureThisEndpoint, AsA_Client { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - } - } - - public class MyProductionProfile : Production {} - - public class MyProductionLogging : NServiceBus.Hosting.Profiles.IConfigureLoggingForProfile - { - public void Configure(IConfigureThisEndpoint specifier) - { - Console.WriteLine("I'm going to do my custom logging setup in here using my own profile."); - } - } -} diff --git a/IntegrationTests/GenericHost/Logging/Logging.csproj b/IntegrationTests/GenericHost/Logging/Logging.csproj deleted file mode 100644 index 1dc371621dc..00000000000 --- a/IntegrationTests/GenericHost/Logging/Logging.csproj +++ /dev/null @@ -1,64 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {15E0163A-315E-4473-9BF3-DD9EA128EBD7} - Library - Properties - Logging - Logging - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - False - ..\..\..\binaries\NServiceBus.Core.dll - - - False - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - Logging.MyProductionProfile - false - - \ No newline at end of file diff --git a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/App.config b/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/App.config deleted file mode 100644 index 88101d5902d..00000000000 --- a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/App.config +++ /dev/null @@ -1,14 +0,0 @@ - - - -
-
-
- - - - - - - - diff --git a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/EndpointConfig.cs b/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/EndpointConfig.cs deleted file mode 100644 index be00feb183d..00000000000 --- a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/EndpointConfig.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using NServiceBus; -using NServiceBus.Logging; - -namespace LoggingWithConfigurableThreshold -{ - public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantToRunWhenBusStartsAndStops - { - static ILog logger = LogManager.GetLogger("A"); - - public void Start() - { - Console.WriteLine("The WARN threshold has been set in the config file."); - - logger.Debug("This should not appear"); - logger.Warn("This should appear"); - } - - public void Stop() - { - } - - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - } - } -} diff --git a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/LoggingWithConfigurableThreshold.csproj b/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/LoggingWithConfigurableThreshold.csproj deleted file mode 100644 index a9d30bf8bce..00000000000 --- a/IntegrationTests/GenericHost/LoggingWithConfigurableThreshold/LoggingWithConfigurableThreshold.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {3BF3849F-A0F5-4B9A-BB8F-12D4219F7787} - Library - Properties - LoggingWithConfigurableThreshold - LoggingWithConfigurableThreshold - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - False - ..\..\..\binaries\NServiceBus.Core.dll - - - False - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - - - - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - Logging.MyProductionProfile - false - - \ No newline at end of file diff --git a/IntegrationTests/MessageMutators/Client/App.config b/IntegrationTests/MessageMutators/Client/App.config deleted file mode 100644 index 0d7bcd07ba5..00000000000 --- a/IntegrationTests/MessageMutators/Client/App.config +++ /dev/null @@ -1,18 +0,0 @@ - - - -
-
-
- - - - - - - - - - - - diff --git a/IntegrationTests/MessageMutators/Client/Client.cs b/IntegrationTests/MessageMutators/Client/Client.cs deleted file mode 100644 index be878fc80f4..00000000000 --- a/IntegrationTests/MessageMutators/Client/Client.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using Messages; -using NServiceBus; - -namespace Client -{ - public class EndpointConfig : IConfigureThisEndpoint, AsA_Client - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - } - } - - public class Runner : IWantToRunWhenBusStartsAndStops - { - public IBus Bus { get; set; } - - public void Start() - { - Console.WriteLine("Press 's' to send a valid message, press 'e' to send a failed message. To exit, 'q'\n"); - - string cmd; - - while ((cmd = Console.ReadKey().Key.ToString().ToLower()) != "q") - { - switch (cmd) - { - case "s": - Bus.Send(m => - { - m.ProductId = "XJ128"; - m.ProductName = "Milk"; - m.ListPrice = 4; - m.SellEndDate = new DateTime(2012, 1, 3); - // 7MB. MSMQ should throw an exception, but it will not since the buffer will be compressed - // before it reaches MSMQ. - m.Image = new byte[1024*1024*7]; - }); - break; - case "e": - try - { - Bus.Send(m => - { - m.ProductId = "XJ128"; - m.ProductName = "Milk Milk Milk Milk Milk"; - m.ListPrice = 15; - m.SellEndDate = new DateTime(2011, 1, 3); - // 7MB. MSMQ should throw an exception, but it will not since the buffer will be compressed - // before it reaches MSMQ. - m.Image = new byte[1024*1024*7]; - }); - } - //Just to allow the sample to keep running. - catch - { - } - break; - } - } - } - - public void Stop() - { - } - } -} \ No newline at end of file diff --git a/IntegrationTests/MessageMutators/Client/Client.csproj b/IntegrationTests/MessageMutators/Client/Client.csproj deleted file mode 100644 index e2abb5ce277..00000000000 --- a/IntegrationTests/MessageMutators/Client/Client.csproj +++ /dev/null @@ -1,89 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {9AF57290-9CEE-4934-BDB4-D201F1EABE49} - Library - Properties - Client - Client - v4.5 - 512 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - False - ..\..\..\binaries\NServiceBus.Host.exe - - - - 3.5 - - - - - - - - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355} - MessageMutators - - - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A} - Messages - - - - - Designer - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - false - - \ No newline at end of file diff --git a/IntegrationTests/MessageMutators/MessageMutator/HookMyMessageMutators.cs b/IntegrationTests/MessageMutators/MessageMutator/HookMyMessageMutators.cs deleted file mode 100644 index bb97aa99a15..00000000000 --- a/IntegrationTests/MessageMutators/MessageMutator/HookMyMessageMutators.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NServiceBus; - -namespace MessageMutators -{ - public class HookMyMessageMutators : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => - { - c.ConfigureComponent( - DependencyLifecycle.InstancePerCall); - c.ConfigureComponent( - DependencyLifecycle.InstancePerCall); - }); - } - } -} diff --git a/IntegrationTests/MessageMutators/MessageMutator/MessageMutators.csproj b/IntegrationTests/MessageMutators/MessageMutator/MessageMutators.csproj deleted file mode 100644 index e26a4bc21ee..00000000000 --- a/IntegrationTests/MessageMutators/MessageMutator/MessageMutators.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355} - Library - Properties - MessageMutators - MessageMutators - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A} - Messages - - - - \ No newline at end of file diff --git a/IntegrationTests/MessageMutators/MessageMutator/TransportMessageCompressionMutator.cs b/IntegrationTests/MessageMutators/MessageMutator/TransportMessageCompressionMutator.cs deleted file mode 100644 index cefe4be74c5..00000000000 --- a/IntegrationTests/MessageMutators/MessageMutator/TransportMessageCompressionMutator.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; -using System.IO.Compression; -using NServiceBus.Logging; -using NServiceBus.MessageMutator; -using NServiceBus; -using NServiceBus.Unicast.Messages; - -namespace MessageMutators -{ - public class TransportMessageCompressionMutator : IMutateTransportMessages - { - private static readonly ILog Logger = LogManager.GetLogger("TransportMessageCompressionMutator"); - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - Logger.Info("transportMessage.Body size before compression: " + transportMessage.Body.Length); - - var mStream = new MemoryStream(transportMessage.Body); - var outStream = new MemoryStream(); - - using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress)) - { - mStream.CopyTo(tinyStream); - } - // copy the compressed buffer only after the GZipStream is disposed, - // otherwise, not all the compressed message will be copied. - transportMessage.Body = outStream.ToArray(); - transportMessage.Headers["IWasCompressed"] = "true"; - Logger.Info("transportMessage.Body size after compression: " + transportMessage.Body.Length); - } - - public void MutateIncoming(TransportMessage transportMessage) - { - if (!transportMessage.Headers.ContainsKey("IWasCompressed")) - return; - using (var bigStream = new GZipStream(new MemoryStream(transportMessage.Body), CompressionMode.Decompress)) - { - var bigStreamOut = new MemoryStream(); - bigStream.CopyTo(bigStreamOut); - transportMessage.Body = bigStreamOut.ToArray(); - } - } - } -} diff --git a/IntegrationTests/MessageMutators/MessageMutator/ValidationMessageMutator.cs b/IntegrationTests/MessageMutators/MessageMutator/ValidationMessageMutator.cs deleted file mode 100644 index 536c5845d1e..00000000000 --- a/IntegrationTests/MessageMutators/MessageMutator/ValidationMessageMutator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Text; -using NServiceBus.Logging; -using NServiceBus.MessageMutator; - -namespace MessageMutators -{ - public class ValidationMessageMutator : IMessageMutator - { - private static readonly ILog Logger = LogManager.GetLogger("ValidationMessageMutator"); - - public object MutateOutgoing(object message) - { - ValidateDataAnnotations(message); - return message; - } - - public object MutateIncoming(object message) - { - ValidateDataAnnotations(message); - return message; - } - - private static void ValidateDataAnnotations(Object message) - { - var context = new ValidationContext(message, null, null); - var results = new List(); - - var isValid = Validator.TryValidateObject(message, context, results, true); - - if (isValid) - { - Logger.Info("Validation succeeded for message: " + message.ToString()); - return; - } - - var errorMessage = new StringBuilder(); - errorMessage.Append( - string.Format("Validation failed for message {0}, with the following error/s: " + Environment.NewLine, - message.ToString())); - - foreach (var validationResult in results) - errorMessage.Append(validationResult.ErrorMessage + Environment.NewLine); - - Logger.Error(errorMessage.ToString()); - throw new Exception(errorMessage.ToString()); - } - } -} diff --git a/IntegrationTests/MessageMutators/MessageMutators.sln b/IntegrationTests/MessageMutators/MessageMutators.sln deleted file mode 100644 index ca55ffb0637..00000000000 --- a/IntegrationTests/MessageMutators/MessageMutators.sln +++ /dev/null @@ -1,40 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messages", "Messages\Messages.csproj", "{E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{9AF57290-9CEE-4934-BDB4-D201F1EABE49}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{2FAF88E7-8FC9-4AFA-B265-9085F983C702}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageMutators", "MessageMutator\MessageMutators.csproj", "{B5B3A66C-7CAD-40A3-ACBC-B82619E62355}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A}.Release|Any CPU.Build.0 = Release|Any CPU - {9AF57290-9CEE-4934-BDB4-D201F1EABE49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9AF57290-9CEE-4934-BDB4-D201F1EABE49}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AF57290-9CEE-4934-BDB4-D201F1EABE49}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9AF57290-9CEE-4934-BDB4-D201F1EABE49}.Release|Any CPU.Build.0 = Release|Any CPU - {2FAF88E7-8FC9-4AFA-B265-9085F983C702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2FAF88E7-8FC9-4AFA-B265-9085F983C702}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2FAF88E7-8FC9-4AFA-B265-9085F983C702}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2FAF88E7-8FC9-4AFA-B265-9085F983C702}.Release|Any CPU.Build.0 = Release|Any CPU - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/IntegrationTests/MessageMutators/Messages/CreateProductCommand.cs b/IntegrationTests/MessageMutators/Messages/CreateProductCommand.cs deleted file mode 100644 index 0ad6eeab5dd..00000000000 --- a/IntegrationTests/MessageMutators/Messages/CreateProductCommand.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using NServiceBus; - -namespace Messages -{ - public class CreateProductCommand : ICommand - { - [Required] - public string ProductId { get; set; } - - [StringLength(20, ErrorMessage = "The Product Name value cannot exceed 20 characters. ")] - public string ProductName { get; set; } - - [Range(1, 5)] - public decimal ListPrice { get; set; } - - [DateRange(ErrorMessage = "Value for {0} must be between {1} and {2}")] - public DateTime SellEndDate { get; set; } - - public byte[] Image { get; set; } - - public override string ToString() - { - return string.Format( - "CreateProductCommand: ProductId={0}, ProductName={1}, ListPrice={2} SellEndDate={3} Image (length)={4}", - ProductId, ProductName, ListPrice, SellEndDate, (Image == null ? 0: Image.Length)); - } - } - /// - /// Custom Range Attribute for Dates range. - /// - class DateRangeAttribute : RangeAttribute - { - /// - /// Constructor - /// - public DateRangeAttribute() : - base(typeof(DateTime), (new DateTime(2012, 1, 1)).ToString(CultureInfo.InvariantCulture), (new DateTime(2012, 5, 1)).ToString(CultureInfo.InvariantCulture)) - { - - } - } -} diff --git a/IntegrationTests/MessageMutators/Messages/Messages.csproj b/IntegrationTests/MessageMutators/Messages/Messages.csproj deleted file mode 100644 index 0580d0e446c..00000000000 --- a/IntegrationTests/MessageMutators/Messages/Messages.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A} - Library - Properties - Messages - Messages - v4.5 - 512 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - - - 3.5 - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/MessageMutators/Server/App.config b/IntegrationTests/MessageMutators/Server/App.config deleted file mode 100644 index 6d60b36915b..00000000000 --- a/IntegrationTests/MessageMutators/Server/App.config +++ /dev/null @@ -1,9 +0,0 @@ - - - -
-
- - - - diff --git a/IntegrationTests/MessageMutators/Server/Server.cs b/IntegrationTests/MessageMutators/Server/Server.cs deleted file mode 100644 index def2c0f6eaa..00000000000 --- a/IntegrationTests/MessageMutators/Server/Server.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Messages; -using NServiceBus; - -namespace Server -{ - public class EndpointConfig : IConfigureThisEndpoint, AsA_Server - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - } - } - - public class Handler : IHandleMessages - { - public void Handle(CreateProductCommand createProductCommand) - { - Console.WriteLine("Received a CreateProductCommand message: " + createProductCommand); - } - } -} diff --git a/IntegrationTests/MessageMutators/Server/Server.csproj b/IntegrationTests/MessageMutators/Server/Server.csproj deleted file mode 100644 index ce3ae59ea98..00000000000 --- a/IntegrationTests/MessageMutators/Server/Server.csproj +++ /dev/null @@ -1,83 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {2FAF88E7-8FC9-4AFA-B265-9085F983C702} - Library - Properties - Server - Server - v4.5 - 512 - publish\ - true - Disk - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - False - ..\..\..\binaries\NServiceBus.Host.exe - - - - 3.5 - - - - - - - - {B5B3A66C-7CAD-40A3-ACBC-B82619E62355} - MessageMutators - - - {E62F2DF8-9E8F-4512-9FD9-C97C97AEC15A} - Messages - - - - - Designer - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - false - - \ No newline at end of file diff --git a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/Program.cs b/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/Program.cs deleted file mode 100644 index e928f428b6e..00000000000 --- a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using NServiceBus; - -namespace SendOnlyEndpoint.Custom -{ - public class Program - { - static void Main() - { - var configuration = new BusConfiguration(); - - using (var bus = Bus.CreateSendOnly(configuration)) - { - bus.Send("SendOnlyDestination@someserver",new TestMessage()); - } - - Console.WriteLine("Message sent to remote endpoint, you can verify this by looking at the outgoing queues in you msmq MMC-snapin"); - Console.WriteLine("Press any key to exit"); - - Console.ReadKey(); - } - } - - public class TestMessage : IMessage{} -} diff --git a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/SendOnlyEndpoint.Custom.csproj b/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/SendOnlyEndpoint.Custom.csproj deleted file mode 100644 index 1ebe2b013bb..00000000000 --- a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.Custom/SendOnlyEndpoint.Custom.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4} - Exe - Properties - SendOnlyEndpoint.Custom - SendOnlyEndpoint.Custom - v4.5 - 512 - ..\ - true - - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.sln b/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.sln deleted file mode 100644 index 2ccc6ca3907..00000000000 --- a/IntegrationTests/SendOnlyEndpoint/SendOnlyEndpoint.sln +++ /dev/null @@ -1,35 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendOnlyEndpoint.Custom", "SendOnlyEndpoint.Custom\SendOnlyEndpoint.Custom.csproj", "{52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Debug|Any CPU.ActiveCfg = Debug|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Debug|x86.ActiveCfg = Debug|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Debug|x86.Build.0 = Debug|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Release|Any CPU.ActiveCfg = Release|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Release|Mixed Platforms.Build.0 = Release|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Release|x86.ActiveCfg = Release|x86 - {52E814AA-CD2D-48F5-AD5C-38D8335DEBF4}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.0\lib\NET35;packages\Unity.Interception.2.1.505.0\lib\NET35 - EndGlobalSection -EndGlobal diff --git a/IntegrationTests/TimeoutManager/MyServer/App.config b/IntegrationTests/TimeoutManager/MyServer/App.config deleted file mode 100644 index 649ee50d75d..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/App.config +++ /dev/null @@ -1,17 +0,0 @@ - - - -
-
-
-
- - - - - - - - - - diff --git a/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessage.cs b/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessage.cs deleted file mode 100644 index a121617f9c4..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MyServer.DeferedProcessing -{ - using System; - using NServiceBus; - - public class DeferredMessage:IMessage - { - public DateTime ProcessAt { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessageHandler.cs b/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessageHandler.cs deleted file mode 100644 index 84010f1a5b6..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/DeferedProcessing/DeferredMessageHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MyServer.DeferedProcessing -{ - using System; - using NServiceBus; - - public class DeferredMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(DeferredMessage message) - { - if (!Bus.CurrentMessageContext.Headers.ContainsKey(Headers.IsDeferredMessage)) - { - LogMessage("Message will be processed at " + message.ProcessAt.ToLongTimeString()); - - Bus.Defer(message.ProcessAt, message); - return; - } - - LogMessage("Deferred message processed"); - } - - static void LogMessage(string message) - { - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), message); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/EndpointConfig.cs b/IntegrationTests/TimeoutManager/MyServer/EndpointConfig.cs deleted file mode 100644 index 6ff8edc427d..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/EndpointConfig.cs +++ /dev/null @@ -1,48 +0,0 @@ -using NServiceBus.Persistence; -using NServiceBus.Persistence.Legacy; -using NServiceBus.Unicast.Messages; - -namespace MyServer -{ - using NServiceBus; - using NServiceBus.MessageMutator; - - public class EndpointConfig : IConfigureThisEndpoint, AsA_Server - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - configuration.UsePersistence().For(Storage.Subscriptions); - } - } - - /// - /// This mutator makes sure that the tenant id is propagated to all outgoing messages - /// - public class TenantPropagatingMutator : IMutateOutgoingTransportMessages, INeedInitialization - { - public IBus Bus { get; set; } - - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - if (Bus.CurrentMessageContext == null) - { - return; - } - - if (!Bus.CurrentMessageContext.Headers.ContainsKey("tenant")) - { - return; - } - - transportMessage.Headers["tenant"] = Bus.CurrentMessageContext.Headers["tenant"]; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent( - DependencyLifecycle.InstancePerCall)); - } - } -} diff --git a/IntegrationTests/TimeoutManager/MyServer/MyServer.csproj b/IntegrationTests/TimeoutManager/MyServer/MyServer.csproj deleted file mode 100644 index e0f40fe19f6..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/MyServer.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5B4F9AC7-B891-4D6A-8804-12B6AF89BE53} - Library - Properties - MyServer - MyServer - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - NServiceBus.Integration - true - false - - \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessage.cs b/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessage.cs deleted file mode 100644 index 202bb433f87..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessage.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MyServer.PerformanceTest -{ - using NServiceBus; - - public class PerformanceTestMessage:IMessage - { - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessageHandler.cs b/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessageHandler.cs deleted file mode 100644 index 7175fc296d6..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/PerformanceTest/PerformanceTestMessageHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MyServer.PerformanceTest -{ - using System; - using System.Collections.Concurrent; - using NServiceBus; - - public class PerformanceTestMessageHandler : IHandleMessages - { - public static ConcurrentBag receivedMessages = new ConcurrentBag(); - - public static int NumExpectedMessages; - public static DateTime TimeStarted; - public static DateTime TimeEnded; - - public IBus Bus { get; set; } - - public void Handle(PerformanceTestMessage message) - { - receivedMessages.Add(Bus.CurrentMessageContext.Id); - - Console.WriteLine("Message {0}({1})", receivedMessages.Count, NumExpectedMessages); - - if (NumExpectedMessages == receivedMessages.Count) - { - TimeEnded = DateTime.UtcNow; - Console.WriteLine("Test finished, total time: {0}", TimeEnded - TimeStarted); - } - - if (receivedMessages.Count > NumExpectedMessages && NumExpectedMessages > 0) - throw new InvalidOperationException("More messages than expected received"); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Saga/MyTimeOutState.cs b/IntegrationTests/TimeoutManager/MyServer/Saga/MyTimeOutState.cs deleted file mode 100644 index 145b2543afd..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Saga/MyTimeOutState.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MyServer.Saga -{ - public class MyTimeOutState - { - public int SomeValue { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSaga.cs b/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSaga.cs deleted file mode 100644 index 7a85765a12c..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSaga.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace MyServer.Saga -{ - using System; - using NServiceBus.Saga; - - public class SimpleSaga:Saga, - IAmStartedByMessages, - IHandleTimeouts - { - public void Handle(StartSagaMessage message) - { - Data.OrderId = message.OrderId; - var someState = new Random().Next(10); - - RequestTimeout(TimeSpan.FromSeconds(10), t => t.SomeValue = someState); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(s => s.OrderId).ToSaga(m => m.OrderId); - } - - void LogMessage(string message) - { - Console.WriteLine("{0} - {1} - SagaId:{2}", DateTime.Now.ToLongTimeString(), message, Data.Id); - } - - public void Timeout(MyTimeOutState state) - { - LogMessage("Timeout fired, with state: " + state.SomeValue); - - LogMessage("Marking the saga as complete, be aware that this will remove the document from the storage"); - MarkAsComplete(); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSagaData.cs b/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSagaData.cs deleted file mode 100644 index 0fe74ad51f6..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Saga/SimpleSagaData.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MyServer.Saga -{ - using System; - using NServiceBus.Saga; - - public class SimpleSagaData : IContainSagaData - { - public Guid Id { get; set; } - public string Originator { get; set; } - public string OriginalMessageId { get; set; } - - [Unique] - public Guid OrderId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Saga/StartSagaMessage.cs b/IntegrationTests/TimeoutManager/MyServer/Saga/StartSagaMessage.cs deleted file mode 100644 index e579aeb1181..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Saga/StartSagaMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MyServer.Saga -{ - using System; - using NServiceBus; - - public class StartSagaMessage:IMessage - { - public Guid OrderId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATask.cs b/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATask.cs deleted file mode 100644 index 7bb7f511b10..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATask.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NServiceBus; - -namespace MyServer.Scheduling -{ - public class ScheduleATask : IMessage - { - } - - public class ScheduledTaskExecuted : IMessage - { - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskHandler.cs b/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskHandler.cs deleted file mode 100644 index c0a49662168..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using NServiceBus; - -namespace MyServer.Scheduling -{ - public class ScheduleATaskHandler : IHandleMessages - { - private readonly IBus bus; - - public Schedule Schedule { get; set; } - - public ScheduleATaskHandler(IBus bus) - { - this.bus = bus; - } - - public void Handle(ScheduleATask message) - { - Console.WriteLine("Scheduling a task to be executed every 1 minute"); - Schedule.Every(TimeSpan.FromMinutes(1),() => bus.SendLocal(new ScheduledTaskExecuted())); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskToRunAtStartUp.cs b/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskToRunAtStartUp.cs deleted file mode 100644 index 2b6a882d1b7..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduleATaskToRunAtStartUp.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Threading; -using NServiceBus; - -namespace MyServer.Scheduling -{ - public class ScheduleATaskToRunAtStartUp : IWantToRunWhenBusStartsAndStops - { - public Schedule Schedule { get; set; } - public void Start() - { - Schedule.Every(TimeSpan.FromMinutes(5),() => Console.WriteLine("This task was schduled when the host started")); - - Schedule.Every(TimeSpan.FromMinutes(3),"Task with specified name",() => - { - Thread.Sleep(60 * 1000); - Console.WriteLine("This task was schduled when the host started and given a name"); - }); - } - - public void Stop() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduledTaskExecutedHandler.cs b/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduledTaskExecutedHandler.cs deleted file mode 100644 index d8f94cead54..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Scheduling/ScheduledTaskExecutedHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using NServiceBus; - -namespace MyServer.Scheduling -{ - public class ScheduledTaskExecutedHandler : IHandleMessages - { - public void Handle(ScheduledTaskExecuted message) - { - Console.WriteLine("ScheduledTaskExecuted handler executed"); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/MyServer/Starter.cs b/IntegrationTests/TimeoutManager/MyServer/Starter.cs deleted file mode 100644 index b296089675b..00000000000 --- a/IntegrationTests/TimeoutManager/MyServer/Starter.cs +++ /dev/null @@ -1,96 +0,0 @@ -using MyServer.Scheduling; - -namespace MyServer -{ - using System; - using System.Collections.Concurrent; - using DeferedProcessing; - using NServiceBus; - using PerformanceTest; - using Saga; - - class Starter : IWantToRunWhenBusStartsAndStops - { - public IBus Bus { get; set; } - - public void Start() - { - Console.WriteLine("Press 'S' to start the saga"); - Console.WriteLine("Press 'T' to start the saga in multi tenant mode"); - Console.WriteLine("Press 'D' to defer a message 10 seconds"); - Console.WriteLine("Press 'R' to schedule a task"); - Console.WriteLine("To exit, press Ctrl + C"); - - string cmd; - - while ((cmd = Console.ReadKey().Key.ToString().ToLower()) != "q") - { - switch (cmd) - { - case "s": - StartSaga(); - break; - - case "t": - //make sure that the database exists! - StartSaga("MyApp.Tenants.Acme"); - break; - - case "d": - DeferMessage(); - break; - - case "r": - ScheduleTask(); - break; - case "p": - PerformanceTest(); - break; - } - } - } - - void PerformanceTest() - { - var total = 40000; - PerformanceTestMessageHandler.receivedMessages = new ConcurrentBag(); - PerformanceTestMessageHandler.NumExpectedMessages = total; - PerformanceTestMessageHandler.TimeStarted = DateTime.UtcNow; - System.Threading.Tasks.Parallel.For(0, total, _ => Bus.Defer(TimeSpan.FromMinutes(20), new PerformanceTestMessage())); - } - - void DeferMessage() - { - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), "Sending a message to be processed at a later time"); - Bus.SendLocal(new DeferredMessage - { - ProcessAt = DateTime.Now.AddSeconds(10) - }); - } - - void StartSaga(string tenant = "") - { - var message = new StartSagaMessage - { - OrderId = Guid.NewGuid() - }; - if (!string.IsNullOrEmpty(tenant)) - { - Bus.SetMessageHeader(message, "tenant", tenant); - } - - Bus.SendLocal(message); - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), "Saga start message sent"); - } - - void ScheduleTask() - { - // The actual scheduling is done in ScheduleATaskHandler - Bus.SendLocal(new ScheduleATask()); - } - - public void Stop() - { - } - } -} \ No newline at end of file diff --git a/IntegrationTests/TimeoutManager/TimeoutManager.sln b/IntegrationTests/TimeoutManager/TimeoutManager.sln deleted file mode 100644 index 043a15ed266..00000000000 --- a/IntegrationTests/TimeoutManager/TimeoutManager.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyServer", "MyServer\MyServer.csproj", "{5B4F9AC7-B891-4D6A-8804-12B6AF89BE53}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5B4F9AC7-B891-4D6A-8804-12B6AF89BE53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B4F9AC7-B891-4D6A-8804-12B6AF89BE53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B4F9AC7-B891-4D6A-8804-12B6AF89BE53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B4F9AC7-B891-4D6A-8804-12B6AF89BE53}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/IntegrationTests/Unobtrusive/Client/App.config b/IntegrationTests/Unobtrusive/Client/App.config deleted file mode 100644 index d75df73b213..00000000000 --- a/IntegrationTests/Unobtrusive/Client/App.config +++ /dev/null @@ -1,26 +0,0 @@ - - - -
-
-
-
- - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Client/Client.csproj b/IntegrationTests/Unobtrusive/Client/Client.csproj deleted file mode 100644 index d2eff9e7dde..00000000000 --- a/IntegrationTests/Unobtrusive/Client/Client.csproj +++ /dev/null @@ -1,83 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {476D0A88-E281-4BC8-9617-93E53C1F9FE6} - Library - Properties - Client - Client - v4.5 - 512 - ..\..\Unobtrusive\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - Designer - - - - - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F} - Commands - - - {6F35A2BC-639A-429D-8A40-35784600E956} - Events - - - {DD0DC12D-D6DF-47C0-B75A-AEC351164196} - Messages - - - {fa4a7645-09b1-47a7-962d-6d3da27f0bce} - MyConventions - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - NServiceBus.Integration - false - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Client/CommandSender.cs b/IntegrationTests/Unobtrusive/Client/CommandSender.cs deleted file mode 100644 index d43a2af6217..00000000000 --- a/IntegrationTests/Unobtrusive/Client/CommandSender.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace Client -{ - using System; - using Commands; - using Messages; - using NServiceBus; - - public class CommandSender : IWantToRunWhenBusStartsAndStops - { - public IBus Bus { get; set; } - - public void Start() - { - Console.WriteLine("Press 'C' to send a command"); - Console.WriteLine("Press 'R' to send a request"); - Console.WriteLine("Press 'S' to start the saga"); - Console.WriteLine("Press 'E' to send a message that is marked as Express"); - Console.WriteLine("Press 'D' to send a large message that is marked to be sent using Data Bus"); - Console.WriteLine("Press 'X' to send a message that is marked with expiration time."); - Console.WriteLine("To exit, press Ctrl + C"); - - while (true) - { - var cmd = Console.ReadKey().Key.ToString().ToLower(); - switch (cmd) - { - case "c": - SendCommand(); - break; - - case "r": - SendRequest(); - break; - - case "s": - StartSaga(); - break; - - case "e": - Express(); - break; - - case "d": - Data(); - break; - - case "x": - Expiration(); - break; - } - } - } - - /// - /// Shut down server before sending this message, after 30 seconds, the message will be moved to Transactional dead-letter messages queue. - /// - private void Expiration() - { - Bus.Send(m => m.RequestId = Guid.NewGuid()); - Console.WriteLine("message with expiration was sent"); - } - - private void Data() - { - var requestId = Guid.NewGuid(); - - Bus.Send(m => - { - m.RequestId = requestId; - m.LargeDataBus = new byte[1024 * 1024 * 5]; - }); - - Console.WriteLine("Request sent id: " + requestId); - } - - private void Express() - { - var requestId = Guid.NewGuid(); - - Bus.Send(m => - { - m.RequestId = requestId; - }); - - Console.WriteLine("Request sent id: " + requestId); - } - - void StartSaga(string tennant = "") - { - var message = new StartSagaMessage - { - OrderId = Guid.NewGuid() - }; - if (!string.IsNullOrEmpty(tennant)) - { - Bus.SetMessageHeader(message, "tennant", tennant); - } - - Bus.Send(message); - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), "Saga start message sent"); - } - - void SendRequest() - { - var requestId = Guid.NewGuid(); - - Bus.Send(m => - { - m.RequestId = requestId; - }); - - Console.WriteLine("Request sent id: " + requestId); - } - - void SendCommand() - { - var commandId = Guid.NewGuid(); - - Bus.Send(m => - { - m.CommandId = commandId; - m.EncryptedString = "Some sensitive information"; - }) - .Register(outcome => Console.WriteLine("Server returned status: " + outcome)); - - Console.WriteLine("Command sent id: " + commandId); - - } - - public void Stop() - { - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Client/EndpointConfig.cs b/IntegrationTests/Unobtrusive/Client/EndpointConfig.cs deleted file mode 100644 index 6ea58cdcbe7..00000000000 --- a/IntegrationTests/Unobtrusive/Client/EndpointConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Client -{ - using NServiceBus; - - public class EndpointConfig : IConfigureThisEndpoint, AsA_Client - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - configuration.FileShareDataBus(@"..\..\..\DataBusShare\"); - configuration.RijndaelEncryptionService(); - } - } - -} diff --git a/IntegrationTests/Unobtrusive/Client/MyEventHandler.cs b/IntegrationTests/Unobtrusive/Client/MyEventHandler.cs deleted file mode 100644 index 7385a785942..00000000000 --- a/IntegrationTests/Unobtrusive/Client/MyEventHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Client -{ - using System; - using Events; - using NServiceBus; - - public class MyEventHandler : IHandleMessages - { - public void Handle(IMyEvent message) - { - Console.WriteLine("IMyEvent received from server with id:" + message.EventId); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Client/ResponseHandler.cs b/IntegrationTests/Unobtrusive/Client/ResponseHandler.cs deleted file mode 100644 index 678df219628..00000000000 --- a/IntegrationTests/Unobtrusive/Client/ResponseHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Client -{ - using System; - using Messages; - using NServiceBus; - - public class ResponseHandler : IHandleMessages - { - public void Handle(Response message) - { - Console.WriteLine("Response received from server for request with id:" + message.ResponseId); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Commands/CommandStatus.cs b/IntegrationTests/Unobtrusive/Commands/CommandStatus.cs deleted file mode 100644 index 2b1383d8b89..00000000000 --- a/IntegrationTests/Unobtrusive/Commands/CommandStatus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Commands -{ - public enum CommandStatus - { - Ok - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Commands/Commands.csproj b/IntegrationTests/Unobtrusive/Commands/Commands.csproj deleted file mode 100644 index 23e0967853a..00000000000 --- a/IntegrationTests/Unobtrusive/Commands/Commands.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F} - Library - Properties - Commands - Commands - v4.0 - 512 - ..\..\Unobtrusive\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Commands/MyCommand.cs b/IntegrationTests/Unobtrusive/Commands/MyCommand.cs deleted file mode 100644 index 372156b6bc9..00000000000 --- a/IntegrationTests/Unobtrusive/Commands/MyCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Commands -{ - using System; - - public class MyCommand - { - public Guid CommandId { get; set; } - public string EncryptedString { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/DataBusShare/2012-12-06_08/2efd0760-d27e-41e0-9b9b-a2a7aa178fd4 b/IntegrationTests/Unobtrusive/DataBusShare/2012-12-06_08/2efd0760-d27e-41e0-9b9b-a2a7aa178fd4 deleted file mode 100644 index d73518d2311..00000000000 Binary files a/IntegrationTests/Unobtrusive/DataBusShare/2012-12-06_08/2efd0760-d27e-41e0-9b9b-a2a7aa178fd4 and /dev/null differ diff --git a/IntegrationTests/Unobtrusive/Events/Events.csproj b/IntegrationTests/Unobtrusive/Events/Events.csproj deleted file mode 100644 index 17296fea619..00000000000 --- a/IntegrationTests/Unobtrusive/Events/Events.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {6F35A2BC-639A-429D-8A40-35784600E956} - Library - Properties - Events - Events - v4.5 - 512 - ..\..\Unobtrusive\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Events/IMyEvent.cs b/IntegrationTests/Unobtrusive/Events/IMyEvent.cs deleted file mode 100644 index 75cf9f43106..00000000000 --- a/IntegrationTests/Unobtrusive/Events/IMyEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Events -{ - using System; - - public interface IMyEvent - { - Guid EventId { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/Messages/DeferredMessage.cs b/IntegrationTests/Unobtrusive/Messages/DeferredMessage.cs deleted file mode 100644 index 76701acd4e1..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/DeferredMessage.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Messages -{ - public class DeferredMessage - { - - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Messages/LargeMessage.cs b/IntegrationTests/Unobtrusive/Messages/LargeMessage.cs deleted file mode 100644 index d61ff804b0b..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/LargeMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Messages -{ - using System; - - public class LargeMessage - { - public Guid RequestId { get; set; } - public byte[] LargeDataBus { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/Messages/MessageThatExpires.cs b/IntegrationTests/Unobtrusive/Messages/MessageThatExpires.cs deleted file mode 100644 index 00697bbc81e..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/MessageThatExpires.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Messages -{ - using System; - - public class MessageThatExpires - { - public Guid RequestId { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/Messages/Messages.csproj b/IntegrationTests/Unobtrusive/Messages/Messages.csproj deleted file mode 100644 index 899d9fba103..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/Messages.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {DD0DC12D-D6DF-47C0-B75A-AEC351164196} - Library - Properties - Messages - Messages - v4.5 - 512 - ..\..\Unobtrusive\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Messages/Request.cs b/IntegrationTests/Unobtrusive/Messages/Request.cs deleted file mode 100644 index 4525f31b2f6..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/Request.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Messages -{ - using System; - - public class Request - { - public Guid RequestId { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/Messages/RequestExpress.cs b/IntegrationTests/Unobtrusive/Messages/RequestExpress.cs deleted file mode 100644 index 655d84dbc94..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/RequestExpress.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Messages -{ - using System; - - public class RequestExpress - { - public Guid RequestId { get; set; } - } -} diff --git a/IntegrationTests/Unobtrusive/Messages/Response.cs b/IntegrationTests/Unobtrusive/Messages/Response.cs deleted file mode 100644 index bf0aa1b026a..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/Response.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Messages -{ - using System; - - public class Response - { - public Guid ResponseId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Messages/StartSagaMessage.cs b/IntegrationTests/Unobtrusive/Messages/StartSagaMessage.cs deleted file mode 100644 index 639ca576d05..00000000000 --- a/IntegrationTests/Unobtrusive/Messages/StartSagaMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Messages -{ - using System; - - public class StartSagaMessage - { - public Guid OrderId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/MyConventions/MessageConventions.cs b/IntegrationTests/Unobtrusive/MyConventions/MessageConventions.cs deleted file mode 100644 index b201a1555ef..00000000000 --- a/IntegrationTests/Unobtrusive/MyConventions/MessageConventions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace MyConventions -{ - using NServiceBus; - - public class MessageConventions : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Commands")) - .DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Events")) - .DefiningMessagesAs(t => t.Namespace == "Messages") - .DefiningEncryptedPropertiesAs(p => p.Name.StartsWith("Encrypted")) - .DefiningDataBusPropertiesAs(p => p.Name.EndsWith("DataBus")) - .DefiningExpressMessagesAs(t => t.Name.EndsWith("Express")) - .DefiningTimeToBeReceivedAs(t => t.Name.EndsWith("Expires") - ? TimeSpan.FromSeconds(30) - : TimeSpan.MaxValue - ); - } - } -} diff --git a/IntegrationTests/Unobtrusive/MyConventions/MyConventions.csproj b/IntegrationTests/Unobtrusive/MyConventions/MyConventions.csproj deleted file mode 100644 index e1259c28852..00000000000 --- a/IntegrationTests/Unobtrusive/MyConventions/MyConventions.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Debug - AnyCPU - {FA4A7645-09B1-47A7-962D-6D3DA27F0BCE} - Library - Properties - MyConventions - MyConventions - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/App.config b/IntegrationTests/Unobtrusive/Server/App.config deleted file mode 100644 index a1874ea144f..00000000000 --- a/IntegrationTests/Unobtrusive/Server/App.config +++ /dev/null @@ -1,11 +0,0 @@ - - - -
-
-
- - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/CatchAllMessageHandler.cs b/IntegrationTests/Unobtrusive/Server/CatchAllMessageHandler.cs deleted file mode 100644 index a6855662a88..00000000000 --- a/IntegrationTests/Unobtrusive/Server/CatchAllMessageHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Server -{ - using System; - using NServiceBus; - - public class CatchAllMessageHandler : IHandleMessages - { - public void Handle(object message) - { - Console.WriteLine("Catch all handler invoked"); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/CommandSender.cs b/IntegrationTests/Unobtrusive/Server/CommandSender.cs deleted file mode 100644 index d96e75c2168..00000000000 --- a/IntegrationTests/Unobtrusive/Server/CommandSender.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Server -{ - using System; - using Events; - using Messages; - using NServiceBus; - - class CommandSender : IWantToRunWhenBusStartsAndStops - { - public IBus Bus { get; set; } - - public void Start() - { - Console.WriteLine("Press 'E' to publish an event"); - Console.WriteLine("Press 'D' to send a deferred message"); - Console.WriteLine("To exit, press Ctrl + C"); - - while (true) - { - var cmd = Console.ReadKey().Key.ToString().ToLower(); - - switch (cmd) - { - case "e": - PublishEvent(); - break; - case "d": - DeferMessage(); - break; - } - } - } - - void DeferMessage() - { - Bus.Defer(TimeSpan.FromSeconds(10), new DeferredMessage()); - Console.WriteLine(); - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), "Sent a message that is deferred for 10 seconds"); - } - - void PublishEvent() - { - var eventId = Guid.NewGuid(); - - Bus.Publish(m => - { - m.EventId = eventId; - }); - Console.WriteLine("Event published, id: " + eventId); - - } - - public void Stop() - { - - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/DeferredMessageHandler.cs b/IntegrationTests/Unobtrusive/Server/DeferredMessageHandler.cs deleted file mode 100644 index 772466cc55c..00000000000 --- a/IntegrationTests/Unobtrusive/Server/DeferredMessageHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Server -{ - using System; - using Messages; - using NServiceBus; - - public class DeferredMessageHandler : IHandleMessages - { - public void Handle(DeferredMessage message) - { - Console.WriteLine(string.Format("{0} - {1}", DateTime.Now.ToLongTimeString(), "Deferred message processed")); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/EndpointConfig.cs b/IntegrationTests/Unobtrusive/Server/EndpointConfig.cs deleted file mode 100644 index bbc8642bebb..00000000000 --- a/IntegrationTests/Unobtrusive/Server/EndpointConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Server -{ - using NServiceBus; - - public class EndpointConfig: IConfigureThisEndpoint, AsA_Server - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - configuration.FileShareDataBus(@"..\..\..\DataBusShare\"); - configuration.RijndaelEncryptionService(); - } - } - -} diff --git a/IntegrationTests/Unobtrusive/Server/ExpressMessagesHandler.cs b/IntegrationTests/Unobtrusive/Server/ExpressMessagesHandler.cs deleted file mode 100644 index 45d52dc9a6d..00000000000 --- a/IntegrationTests/Unobtrusive/Server/ExpressMessagesHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Server -{ - using System; - using Messages; - using NServiceBus; - - public class ExpressMessagesHandler : IHandleMessages - { - public void Handle(RequestExpress message) - { - Console.Out.WriteLine("Message [{0}] received, id: [{1}]", message.GetType(), message.RequestId); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/LargeMessagesHandler.cs b/IntegrationTests/Unobtrusive/Server/LargeMessagesHandler.cs deleted file mode 100644 index 8ed9bfcca5d..00000000000 --- a/IntegrationTests/Unobtrusive/Server/LargeMessagesHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Server -{ - using System; - using Messages; - using NServiceBus; - - public class LargeMessagesHandler : IHandleMessages - { - public void Handle(LargeMessage message) - { - if(message.LargeDataBus != null) - Console.Out.WriteLine("Message [{0}] received, id:{1} and payload {2} bytes", message.GetType(), message.RequestId, message.LargeDataBus.Length); - else - { - Console.Out.WriteLine("Message [{0}] received, id:{1}", message.GetType(), message.RequestId); - } - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/MessagesThatMarkWithExpirationHandler.cs b/IntegrationTests/Unobtrusive/Server/MessagesThatMarkWithExpirationHandler.cs deleted file mode 100644 index 6d3d0ee7312..00000000000 --- a/IntegrationTests/Unobtrusive/Server/MessagesThatMarkWithExpirationHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Server -{ - using System; - using Messages; - using NServiceBus; - - public class MessagesThatMarkWithExpirationHandler : IHandleMessages - { - public void Handle(MessageThatExpires message) - { - Console.Out.WriteLine("Message [{0}] received, id: [{1}]", message.GetType(), message.RequestId); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/MyCommandHandler.cs b/IntegrationTests/Unobtrusive/Server/MyCommandHandler.cs deleted file mode 100644 index 106134754a5..00000000000 --- a/IntegrationTests/Unobtrusive/Server/MyCommandHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Server -{ - using System; - using Commands; - using NServiceBus; - - public class MyCommandHandler : IHandleMessages - { - readonly IBus bus; - - public MyCommandHandler(IBus bus) - { - this.bus = bus; - } - - public void Handle(MyCommand message) - { - Console.WriteLine("Command received, id:" + message.CommandId); - Console.WriteLine("EncryptedString:" + message.EncryptedString); - - bus.Return(CommandStatus.Ok); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/RequestMessageHandler.cs b/IntegrationTests/Unobtrusive/Server/RequestMessageHandler.cs deleted file mode 100644 index d1e518c2e4f..00000000000 --- a/IntegrationTests/Unobtrusive/Server/RequestMessageHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Server -{ - using System; - using Messages; - using NServiceBus; - - public class RequestMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(Request message) - { - Console.WriteLine("Request received with id:" + message.RequestId); - - Bus.Reply(new Response - { - ResponseId = message.RequestId - }); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/Saga/MyTimeOutState.cs b/IntegrationTests/Unobtrusive/Server/Saga/MyTimeOutState.cs deleted file mode 100644 index a4f121a9fd8..00000000000 --- a/IntegrationTests/Unobtrusive/Server/Saga/MyTimeOutState.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Server.Saga -{ - public class MyTimeOutState - { - public int SomeValue { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/Saga/SimpleSaga.cs b/IntegrationTests/Unobtrusive/Server/Saga/SimpleSaga.cs deleted file mode 100644 index 439ceede293..00000000000 --- a/IntegrationTests/Unobtrusive/Server/Saga/SimpleSaga.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Messages; -using System; -using NServiceBus.Saga; - -namespace Server.Saga -{ - public class SimpleSaga : Saga, - IAmStartedByMessages, - IHandleTimeouts - { - public void Handle(StartSagaMessage message) - { - Data.OrderId = message.OrderId; - var someState = new Random().Next(10); - - LogMessage("Requesting a custom timeout v3.0 style, state: " + someState); - RequestTimeout(TimeSpan.FromSeconds(10), new MyTimeOutState - { - SomeValue = someState - }); - } - - void LogMessage(string message) - { - Console.WriteLine("{0} - {1}", DateTime.Now.ToLongTimeString(), message); - } - - public void Timeout(MyTimeOutState state) - { - LogMessage("v3.0 Timeout fired, with state: " + state.SomeValue); - - LogMessage("Marking the saga as complete, be aware that this will remove the document from the storage"); - MarkAsComplete(); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(s => s.OrderId).ToSaga(m => m.OrderId); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/Saga/SimpleSagaData.cs b/IntegrationTests/Unobtrusive/Server/Saga/SimpleSagaData.cs deleted file mode 100644 index 23c82d2f9c2..00000000000 --- a/IntegrationTests/Unobtrusive/Server/Saga/SimpleSagaData.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using NServiceBus.Saga; - -namespace Server.Saga -{ - public class SimpleSagaData : IContainSagaData - { - public Guid Id { get; set; } - public string Originator { get; set; } - public string OriginalMessageId { get; set; } - - [Unique] - public Guid OrderId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Server/Server.csproj b/IntegrationTests/Unobtrusive/Server/Server.csproj deleted file mode 100644 index b90372ed9c2..00000000000 --- a/IntegrationTests/Unobtrusive/Server/Server.csproj +++ /dev/null @@ -1,92 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {75424F0A-458B-42DA-9DDD-600367893A93} - Library - Properties - Server - Server - v4.5 - 512 - ..\..\Unobtrusive\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F} - Commands - - - {6F35A2BC-639A-429D-8A40-35784600E956} - Events - - - {DD0DC12D-D6DF-47C0-B75A-AEC351164196} - Messages - - - {fa4a7645-09b1-47a7-962d-6d3da27f0bce} - MyConventions - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - NServiceBus.Integration - false - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Tests/RequestMessageHandlerShould.cs b/IntegrationTests/Unobtrusive/Tests/RequestMessageHandlerShould.cs deleted file mode 100644 index 5d4cbfb5968..00000000000 --- a/IntegrationTests/Unobtrusive/Tests/RequestMessageHandlerShould.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Tests -{ - using System; - using Messages; - using NServiceBus.Testing; - using NUnit.Framework; - using Server; - - [TestFixture] - public class RequestMessageHandlerShould - { - [TestFixtureSetUp] - public void TestSetup() - { - Test.Initialize(configuration => configuration.Conventions().DefiningMessagesAs(t => t.Namespace == "Messages")); - } - - [Test] - public void ReplyWithResponseIdEqualToRequestId() - { - var requestId = Guid.NewGuid(); - - Test.Handler() - .ExpectReply(m => m.ResponseId == requestId) - .OnMessage(m => m.RequestId = requestId); - } - } -} diff --git a/IntegrationTests/Unobtrusive/Tests/Tests.csproj b/IntegrationTests/Unobtrusive/Tests/Tests.csproj deleted file mode 100644 index c260d86274d..00000000000 --- a/IntegrationTests/Unobtrusive/Tests/Tests.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5} - Library - Properties - Tests - Tests - v4.5 - 512 - ..\..\Unobtrusive\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\..\binaries\NServiceBus.Core.dll - - - ..\..\..\binaries\NServiceBus.Testing.dll - - - False - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - - - - - - - - - - - - - {DD0DC12D-D6DF-47C0-B75A-AEC351164196} - Messages - - - {75424F0A-458B-42DA-9DDD-600367893A93} - Server - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Tests/packages.config b/IntegrationTests/Unobtrusive/Tests/packages.config deleted file mode 100644 index ad37a5282e4..00000000000 --- a/IntegrationTests/Unobtrusive/Tests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/IntegrationTests/Unobtrusive/Unobtrusive.sln b/IntegrationTests/Unobtrusive/Unobtrusive.sln deleted file mode 100644 index c6cd6522c9f..00000000000 --- a/IntegrationTests/Unobtrusive/Unobtrusive.sln +++ /dev/null @@ -1,56 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{476D0A88-E281-4BC8-9617-93E53C1F9FE6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{75424F0A-458B-42DA-9DDD-600367893A93}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commands", "Commands\Commands.csproj", "{5AD5EAEF-AF56-49B7-A15D-C0F71780049F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events", "Events\Events.csproj", "{6F35A2BC-639A-429D-8A40-35784600E956}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messages", "Messages\Messages.csproj", "{DD0DC12D-D6DF-47C0-B75A-AEC351164196}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyConventions", "MyConventions\MyConventions.csproj", "{FA4A7645-09B1-47A7-962D-6D3DA27F0BCE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {476D0A88-E281-4BC8-9617-93E53C1F9FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {476D0A88-E281-4BC8-9617-93E53C1F9FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {476D0A88-E281-4BC8-9617-93E53C1F9FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {476D0A88-E281-4BC8-9617-93E53C1F9FE6}.Release|Any CPU.Build.0 = Release|Any CPU - {75424F0A-458B-42DA-9DDD-600367893A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75424F0A-458B-42DA-9DDD-600367893A93}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75424F0A-458B-42DA-9DDD-600367893A93}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75424F0A-458B-42DA-9DDD-600367893A93}.Release|Any CPU.Build.0 = Release|Any CPU - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5AD5EAEF-AF56-49B7-A15D-C0F71780049F}.Release|Any CPU.Build.0 = Release|Any CPU - {6F35A2BC-639A-429D-8A40-35784600E956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F35A2BC-639A-429D-8A40-35784600E956}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F35A2BC-639A-429D-8A40-35784600E956}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F35A2BC-639A-429D-8A40-35784600E956}.Release|Any CPU.Build.0 = Release|Any CPU - {DD0DC12D-D6DF-47C0-B75A-AEC351164196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD0DC12D-D6DF-47C0-B75A-AEC351164196}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD0DC12D-D6DF-47C0-B75A-AEC351164196}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD0DC12D-D6DF-47C0-B75A-AEC351164196}.Release|Any CPU.Build.0 = Release|Any CPU - {5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CFCD3C2-9BD3-4FC6-AF7A-F0481F6818F5}.Release|Any CPU.Build.0 = Release|Any CPU - {FA4A7645-09B1-47A7-962D-6D3DA27F0BCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA4A7645-09B1-47A7-962D-6D3DA27F0BCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA4A7645-09B1-47A7-962D-6D3DA27F0BCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA4A7645-09B1-47A7-962D-6D3DA27F0BCE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/IntegrationTests/WcfIntegration/Client/Client.csproj b/IntegrationTests/WcfIntegration/Client/Client.csproj deleted file mode 100644 index 3a60672c303..00000000000 --- a/IntegrationTests/WcfIntegration/Client/Client.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A} - Exe - Properties - Client - Client - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - False - ..\..\..\binaries\NServiceBus.Core.dll - - - - 3.0 - - - 3.0 - - - - - - - - - - - - {174344DB-E395-4B4E-8385-4F14926E8948} - Messages - - - - \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Client/Program.cs b/IntegrationTests/WcfIntegration/Client/Program.cs deleted file mode 100644 index 6490b53f06b..00000000000 --- a/IntegrationTests/WcfIntegration/Client/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.ServiceModel; -using System.ServiceModel.Channels; -using Messages; - -namespace Client -{ - class Program - { - private static readonly ChannelFactory ChannelFactory = new ChannelFactory(""); - - private static void Main() - { - Console.WriteLine("This will send requests to the CancelOrder WCF service"); - Console.WriteLine("Press 'Enter' to send a message.To exit, Ctrl + C"); - - var client = ChannelFactory.CreateChannel(); - var orderId = 1; - - try - { - while (Console.ReadLine() != null) - { - var message = new CancelOrder - { - OrderId = orderId++ - }; - - Console.WriteLine("Sending message with OrderId {0}.", message.OrderId); - - var returnCode = client.Process(message); - - Console.WriteLine("Error code returned: " + returnCode); - } - } - finally - { - try - { - ((IChannel) client).Close(); - } - catch - { - ((IChannel)client).Abort(); - } - } - } - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Client/app.config b/IntegrationTests/WcfIntegration/Client/app.config deleted file mode 100644 index 1bf44c7a2eb..00000000000 --- a/IntegrationTests/WcfIntegration/Client/app.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Messages/CancelOrder.cs b/IntegrationTests/WcfIntegration/Messages/CancelOrder.cs deleted file mode 100644 index 2c7baa5fb25..00000000000 --- a/IntegrationTests/WcfIntegration/Messages/CancelOrder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using NServiceBus; - -namespace Messages -{ - public class CancelOrder : IMessage - { - public int OrderId { get; set; } - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Messages/ErrorCodes.cs b/IntegrationTests/WcfIntegration/Messages/ErrorCodes.cs deleted file mode 100644 index c010676e7e5..00000000000 --- a/IntegrationTests/WcfIntegration/Messages/ErrorCodes.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Messages -{ - public enum ErrorCodes - { - None, - Fail - } -} diff --git a/IntegrationTests/WcfIntegration/Messages/ICancelOrderService.cs b/IntegrationTests/WcfIntegration/Messages/ICancelOrderService.cs deleted file mode 100644 index 6c92f8df904..00000000000 --- a/IntegrationTests/WcfIntegration/Messages/ICancelOrderService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ServiceModel; - -namespace Messages -{ - /// - /// This is an example of using a service contract interface - /// instead of a service reference. Make sure to include the - /// Action / ReplyAction values that correspond to your - /// service inputs and outputs. A simple way to find out these - /// values is to host the service and inspect the auto-generated - /// WSDL by appending ?wsdl to the URL of the service. - /// - [ServiceContract(Namespace = "http://nservicebus.com")] - public interface ICancelOrderService - { - [OperationContract(Action = "http://nservicebus.com/IWcfServiceOf_CancelOrder_ErrorCodes/Process", ReplyAction = "http://nservicebus.com/IWcfServiceOf_CancelOrder_ErrorCodes/ProcessResponse")] - ErrorCodes Process(CancelOrder request); - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Messages/Messages.csproj b/IntegrationTests/WcfIntegration/Messages/Messages.csproj deleted file mode 100644 index f688150f44c..00000000000 --- a/IntegrationTests/WcfIntegration/Messages/Messages.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {174344DB-E395-4B4E-8385-4F14926E8948} - Library - Properties - Messages - Messages - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - False - ..\..\..\binaries\NServiceBus.Core.dll - - - - 3.0 - - - - - - - - - \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Server/EndpointConfig.cs b/IntegrationTests/WcfIntegration/Server/EndpointConfig.cs deleted file mode 100644 index 1fd2d5e984f..00000000000 --- a/IntegrationTests/WcfIntegration/Server/EndpointConfig.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NServiceBus; - -namespace Server -{ - class EndpointConfig : IConfigureThisEndpoint, AsA_Server - { - public void Customize(BusConfiguration configuration) - { - configuration.UsePersistence(); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Server/Handlers/CancelOrderHandler.cs b/IntegrationTests/WcfIntegration/Server/Handlers/CancelOrderHandler.cs deleted file mode 100644 index a9c60512623..00000000000 --- a/IntegrationTests/WcfIntegration/Server/Handlers/CancelOrderHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Messages; -using NServiceBus; - -namespace Server.Handlers -{ - public class CancelOrderHandler : IHandleMessages - { - private readonly IBus bus; - - public CancelOrderHandler(IBus bus) - { - this.bus = bus; - } - - public void Handle(CancelOrder message) - { - Console.WriteLine("======================================================================"); - - if (message.OrderId % 2 == 0) - bus.Return((int) ErrorCodes.Fail); - else - bus.Return((int) ErrorCodes.None); - } - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Server/Server.csproj b/IntegrationTests/WcfIntegration/Server/Server.csproj deleted file mode 100644 index 211859e0990..00000000000 --- a/IntegrationTests/WcfIntegration/Server/Server.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {86C621B1-CB00-4555-ABB2-A68191BD8D73} - Library - Properties - Server - Server - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - False - ..\..\..\binaries\NServiceBus.Core.dll - - - False - ..\..\..\binaries\NServiceBus.Host.exe - - - - - - - - - - - - - Designer - - - - - {174344DB-E395-4B4E-8385-4F14926E8948} - Messages - - - - - Program - $(ProjectDir)$(OutputPath)NServiceBus.Host.exe - NServiceBus.Lite - false - - \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Server/WebServices/CancelOrderService.cs b/IntegrationTests/WcfIntegration/Server/WebServices/CancelOrderService.cs deleted file mode 100644 index e91815ff9e6..00000000000 --- a/IntegrationTests/WcfIntegration/Server/WebServices/CancelOrderService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Messages; -using NServiceBus; - -namespace Server.WebServices -{ - public class CancelOrderService : WcfService - { - } -} \ No newline at end of file diff --git a/IntegrationTests/WcfIntegration/Server/app.config b/IntegrationTests/WcfIntegration/Server/app.config deleted file mode 100644 index 35f12d34813..00000000000 --- a/IntegrationTests/WcfIntegration/Server/app.config +++ /dev/null @@ -1,37 +0,0 @@ - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/IntegrationTests/WcfIntegration/WcfIntegration.sln b/IntegrationTests/WcfIntegration/WcfIntegration.sln deleted file mode 100644 index e8cfefb543b..00000000000 --- a/IntegrationTests/WcfIntegration/WcfIntegration.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messages", "Messages\Messages.csproj", "{174344DB-E395-4B4E-8385-4F14926E8948}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{86C621B1-CB00-4555-ABB2-A68191BD8D73}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6D39475-05EF-4EF5-87F7-BE4C99A7BC8A}.Release|Any CPU.Build.0 = Release|Any CPU - {174344DB-E395-4B4E-8385-4F14926E8948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {174344DB-E395-4B4E-8385-4F14926E8948}.Debug|Any CPU.Build.0 = Debug|Any CPU - {174344DB-E395-4B4E-8385-4F14926E8948}.Release|Any CPU.ActiveCfg = Release|Any CPU - {174344DB-E395-4B4E-8385-4F14926E8948}.Release|Any CPU.Build.0 = Release|Any CPU - {86C621B1-CB00-4555-ABB2-A68191BD8D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {86C621B1-CB00-4555-ABB2-A68191BD8D73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {86C621B1-CB00-4555-ABB2-A68191BD8D73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {86C621B1-CB00-4555-ABB2-A68191BD8D73}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/README.md b/README.md index 41289e730d3..5de32f91393 100644 --- a/README.md +++ b/README.md @@ -15,103 +15,18 @@ To run NServiceBus, please download and install the setup file from http://parti ## Licenses -### [NHibernate](http://www.hibernate.org/) - -NHibernate is licensed under the LGPL v2.1 license as described here: - -http://www.hibernate.org/license.html - -NHibernate binaries are merged into NServiceBus allowed under the LGPL license terms found here: - -http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt - - -### Iesi.Collections - -Iesi.Collections binaries are merged into NServiceBus allowed under the license terms found here: - -Copyright 2002-2004 by Aidant Systems, Inc., and by Jason Smith. - -Copied from http://www.codeproject.com/csharp/sets.asp#xx703510xx that was posted by JasonSmith 12:13 2 Jan '04 - -Feel free to use this code any way you want to. As a favor to me, you can leave the copyright in there. You never know when someone might recognize your name! - -If you do use the code in a commercial product, I would appreciate hearing about it. This message serves as legal notice that I won't be suing you for royalties! The code is in the public domain. - -On the other hand, I don't provide support. The code is actually simple enough that it shouldn't need it. - - ### [Autofac](http://code.google.com/p/autofac/) -Autofac is licensed under the MIT license as described [here](http://code.google.com/p/autofac/). - -Autofac binaries are linked into the NServiceBus distribution allowed under the license terms found [here](http://www.opensource.org/licenses/mit-license.php). - -### [Spring.NET](http://www.springframework.net) - -Spring.NET is licensed under the Apache license version 2.0 as described [here](http://www.springframework.net/license.html) - -Spring.NET binaries are merged into NServiceBus allowed under the license terms found [here](http://www.apache.org/licenses/LICENSE-2.0.txt). - - -### [Common.Logging](http://netcommon.sourceforge.net) - -Common.Logging is licensed under the Apache License, Version 2.0 as described [here](http://netcommon.sourceforge.net/license.html). - -Common.Logging binaries are merged into NServiceBus allowed under the LGPL license terms found [here](http://www.apache.org/licenses/LICENSE-2.0.txt). - -### [StructureMap](http://structuremap.net) +Autofac is licensed under the MIT license as described [here](https://github.com/autofac/Autofac/blob/master/LICENSE). -StructureMap is licensed under the Apache License, Version 2.0 as described [here](http://docs.structuremap.net/). +Autofac binaries are linked into the NServiceBus distribution allowed under the license terms found [here](https://github.com/autofac/Autofac/blob/master/LICENSE). -StructureMap binaries are linked into the NServiceBus distribution allowed under the license terms found [here](http://www.apache.org/licenses/LICENSE-2.0.txt). +### [Json.NET](http://www.newtonsoft.com/json) -### [Castle](http://www.castleproject.org/) - -Castle is licensed under the Apache License, Version 2.0 as described [here](http://www.castleproject.org/). - -Castle binaries are linked into the NServiceBus distribution allowed under the license terms found [here](http://www.apache.org/licenses/LICENSE-2.0.txt). - -### [Unity](http://unity.codeplex.com) - -Unity is licensed under the MSPL license as described [here](http://unity.codeplex.com/license). - -Unity binaries are linked into the NServiceBus distribution allowed under the license terms described above. - -### [Log4Net](http://logging.apache.org/log4net/) - -Log4Net is licensed under the Apache License, Version 2.0 as described [here](http://logging.apache.org/log4net/license.html). - -Log4Net binaries are linked into the NServiceBus distribution allowed under the license terms described above. - -### [TopShelf](http://topshelf-project.com/) - -TopShelf is licensed under the Apache License, Version 2.0 as described here: - -TopShelf binaries are merged into NServiceBus as allowed under the license terms described [here](http://www.apache.org/licenses/LICENSE-2.0.txt). - - -### [RavenDB](http://ravendb.net) - -RavenDB is under both a OSS and a commercial license described [here](http://ravendb.net/licensing). - -The commercial version can be used free of charge for NServiceBus specific storage needs like: - -Subscriptions, Sagas, Timeouts, etc - -Application specific use requires a paid RavenDB license - -RavenDB binaries are linked into the NServiceBus distribution allowed under the license terms described above. - -### [ActiveMQ](http://activemq.apache.org) - -ActiveMQ is licensed under the Apache 2.0 licence as described [here](http://activemq.apache.org/what-is-the-license.html). - -The ActiveMQ client is referenced by NServiceBus +Json.NET is licensed under the MIT license as described [here](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md). +Json.NET binaries are linked into the NServiceBus distribution allowed under the license terms found [here](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md). ### RijndaelEncryptionService Taken from [rhino-esb](https://github.com/hibernating-rhinos/rhino-esb/blob/master/Rhino.ServiceBus/Impl/RijndaelEncryptionService.cs) under [this license](https://github.com/hibernating-rhinos/rhino-esb/blob/master/license.txt) - - diff --git a/packaging/nuget/nservicebus.acceptancetesting.nuspec b/packaging/nuget/nservicebus.acceptancetesting.nuspec index bfee818b580..2a7d67945e3 100644 --- a/packaging/nuget/nservicebus.acceptancetesting.nuspec +++ b/packaging/nuget/nservicebus.acceptancetesting.nuspec @@ -2,23 +2,23 @@ NServiceBus.AcceptanceTesting - NServiceBus Acceptance Tests + NServiceBus Acceptance Testing Framework $version$ - NServiceBus Ltd - Udi Dahan, Andreas Ohlund, John Simons - http://particular.net/LicenseAgreement - http://particular.net/ - http://s3.amazonaws.com/nuget.images/NServiceBus_32.png - true - Acceptance tests for nservicebus core functionality + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ + Acceptance testing framework for NServiceBus endpoints. This is an unsupported package. - Copyright 2010-2014 NServiceBus. All rights reserved + $copyright$ nservicebus servicebus msmq cqrs publish subscribe - + \ No newline at end of file diff --git a/packaging/nuget/nservicebus.acceptancetests.nuspec b/packaging/nuget/nservicebus.acceptancetests.nuspec index 1198e5d4535..9b1ec3542ec 100644 --- a/packaging/nuget/nservicebus.acceptancetests.nuspec +++ b/packaging/nuget/nservicebus.acceptancetests.nuspec @@ -4,28 +4,31 @@ NServiceBus.AcceptanceTests.Sources Source only package containing the NServiceBus acceptance test suite $version$ - NServiceBus Ltd - Udi Dahan, Andreas Ohlund, John Simons - http://particular.net/LicenseAgreement - http://particular.net/ - http://s3.amazonaws.com/nuget.images/NServiceBus_32.png - true + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ Acceptance tests for nservicebus core functionality - Copyright 2010-2014 NServiceBus. All rights reserved + $copyright$ nservicebus servicebus msmq cqrs publish subscribe - - - - - - - + + + + + + + - + diff --git a/packaging/nuget/nservicebus.containertests.nuspec b/packaging/nuget/nservicebus.containertests.nuspec index 3a034240859..b9b9fae58af 100644 --- a/packaging/nuget/nservicebus.containertests.nuspec +++ b/packaging/nuget/nservicebus.containertests.nuspec @@ -4,19 +4,19 @@ NServiceBus.ContainerTests.Sources Source only package containing the NServiceBus container test suite $version$ - NServiceBus Ltd - NServiceBus Ltd - http://particular.net/LicenseAgreement - http://particular.net/ - http://s3.amazonaws.com/nuget.images/NServiceBus_32.png - true + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ Container tests for nservicebus core functionality - Copyright 2010-2014 NServiceBus. All rights reserved + $copyright$ nservicebus servicebus msmq cqrs publish subscribe - + diff --git a/packaging/nuget/nservicebus.core.nuspec b/packaging/nuget/nservicebus.core.nuspec index df600eb438f..c62b87d822c 100644 --- a/packaging/nuget/nservicebus.core.nuspec +++ b/packaging/nuget/nservicebus.core.nuspec @@ -4,26 +4,24 @@ NServiceBus NServiceBus $version$ - NServiceBus Ltd - Udi Dahan, Andreas Ohlund, John Simons - http://particular.net/LicenseAgreement - http://particular.net/ - http://s3.amazonaws.com/nuget.images/NServiceBus_32.png - true - The most popular open-source service bus for .net + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ + The most popular open-source service bus for .NET - Copyright 2010-2014 NServiceBus. All rights reserved + $copyright$ nservicebus servicebus msmq cqrs publish subscribe - - + + - - - + - \ No newline at end of file + diff --git a/packaging/nuget/nservicebus.testing.fakes.nuspec b/packaging/nuget/nservicebus.testing.fakes.nuspec new file mode 100644 index 00000000000..5c4a7fa34de --- /dev/null +++ b/packaging/nuget/nservicebus.testing.fakes.nuspec @@ -0,0 +1,26 @@ + + + + NServiceBus.Testing.Fakes.Sources + Source only package containing fake implementations of various NServiceBus components for unit tests + $version$ + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ + Unit testing fakes and helpers for NServiceBus core functionality + + $copyright$ + + + + + + + + diff --git a/packaging/nuget/nservicebus.transporttests.nuspec b/packaging/nuget/nservicebus.transporttests.nuspec new file mode 100644 index 00000000000..159dacd595b --- /dev/null +++ b/packaging/nuget/nservicebus.transporttests.nuspec @@ -0,0 +1,32 @@ + + + + NServiceBus.TransportTests.Sources + Source only package containing the NServiceBus transport seam test suite + $version$ + $authors$ + $owners$ + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $requireLicenseAcceptance$ + Tests for transport seam implementations + + $copyright$ + $tags$ + + + + + + + + + + + + + diff --git a/packaging/nuget/tools/init.ps1 b/packaging/nuget/tools/init.ps1 index 89c5bc9145f..482f4c91827 100644 --- a/packaging/nuget/tools/init.ps1 +++ b/packaging/nuget/tools/init.ps1 @@ -1,20 +1,5 @@ param($installPath, $toolsPath, $package, $project) -if($toolsPath){ - if (-Not (Get-Module NServiceBus.Powershell)) { - - $pathToNServiceBusPSCmdLets = Join-Path $toolsPath NServiceBus.Powershell.Development.dll - - if(Test-Path $pathToNServiceBusPSCmdLets){ - Import-Module $pathToNServiceBusPSCmdLets - Write-Host "Type 'get-help about_NServiceBus' to see all available NServiceBus commands." - } - else { - Write-Host "NServiceBus powershell module could not be found, no powershell commands will be available" - } - } -} - $nserviceBusKeyPath = "HKCU:SOFTWARE\NServiceBus" $platformKeyPath = "HKCU:SOFTWARE\ParticularSoftware" diff --git a/src/ConventionBasedHandlers/ConventionBasedHandlers.csproj b/src/ConventionBasedHandlers/ConventionBasedHandlers.csproj index 7bffd676d7b..878ef40b8d5 100644 --- a/src/ConventionBasedHandlers/ConventionBasedHandlers.csproj +++ b/src/ConventionBasedHandlers/ConventionBasedHandlers.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -13,6 +13,7 @@ true ..\NServiceBus.snk ..\ + 5 true diff --git a/src/NServiceBus.AcceptanceTesting/ContextAppender.cs b/src/NServiceBus.AcceptanceTesting/ContextAppender.cs new file mode 100644 index 00000000000..6781000afd6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/ContextAppender.cs @@ -0,0 +1,126 @@ +namespace NServiceBus.AcceptanceTesting +{ + using System; + using System.Diagnostics; + using Logging; + + /// + /// This class is written under the assumption that acceptance tests are executed sequentially. + /// + class ContextAppender : ILog + { + public ContextAppender(LogLevel level, Func context) + { + this.level = level; + this.context = context; + } + + public bool IsDebugEnabled => level <= LogLevel.Debug; + public bool IsInfoEnabled => level <= LogLevel.Info; + public bool IsWarnEnabled => level <= LogLevel.Warn; + public bool IsErrorEnabled => level <= LogLevel.Error; + public bool IsFatalEnabled => level <= LogLevel.Fatal; + + + public void Debug(string message) + { + Log(message, LogLevel.Debug); + } + + public void Debug(string message, Exception exception) + { + Log(message, LogLevel.Debug); + } + + public void DebugFormat(string format, params object[] args) + { + var fullMessage = string.Format(format, args); + Log(fullMessage, LogLevel.Debug); + } + + public void Info(string message) + { + Log(message, LogLevel.Info); + } + + + public void Info(string message, Exception exception) + { + var fullMessage = $"{message} {exception}"; + Log(fullMessage, LogLevel.Info); + } + + public void InfoFormat(string format, params object[] args) + { + var fullMessage = string.Format(format, args); + Log(fullMessage, LogLevel.Info); + } + + public void Warn(string message) + { + Log(message, LogLevel.Warn); + } + + public void Warn(string message, Exception exception) + { + var fullMessage = $"{message} {exception}"; + Log(fullMessage, LogLevel.Warn); + } + + public void WarnFormat(string format, params object[] args) + { + var fullMessage = string.Format(format, args); + Log(fullMessage, LogLevel.Warn); + } + + public void Error(string message) + { + Log(message, LogLevel.Error); + } + + public void Error(string message, Exception exception) + { + var fullMessage = $"{message} {exception}"; + Log(fullMessage, LogLevel.Error); + } + + public void ErrorFormat(string format, params object[] args) + { + var fullMessage = string.Format(format, args); + Log(fullMessage, LogLevel.Error); + } + + public void Fatal(string message) + { + Log(message, LogLevel.Fatal); + } + + public void Fatal(string message, Exception exception) + { + var fullMessage = $"{message} {exception}"; + Log(fullMessage, LogLevel.Fatal); + } + + public void FatalFormat(string format, params object[] args) + { + var fullMessage = string.Format(format, args); + Log(fullMessage, LogLevel.Fatal); + } + + void Log(string message, LogLevel messageSeverity) + { + if (level <= messageSeverity) + { + Trace.WriteLine(message); + context().Logs.Enqueue(new ScenarioContext.LogItem + { + Level = messageSeverity, + Message = message + }); + } + } + + LogLevel level; + Func context; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/ContextAppenderFactory.cs b/src/NServiceBus.AcceptanceTesting/ContextAppenderFactory.cs new file mode 100644 index 00000000000..3100acd7eb0 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/ContextAppenderFactory.cs @@ -0,0 +1,30 @@ +namespace NServiceBus.AcceptanceTesting +{ + using System; + using Logging; + + class ContextAppenderFactory : ILoggerFactory + { + static ScenarioContext context; + + + /// + /// Because ILoggerFactory interface methods are only used in a static context. This is the only way to set the currently executing context. + /// + /// The new context to be set + public static void SetContext(ScenarioContext newContext) + { + context = newContext; + } + + public ILog GetLogger(Type type) + { + return GetLogger(type.FullName); + } + + public ILog GetLogger(string name) + { + return new ContextAppender(context.LogLevel, () => context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Customization/Conventions.cs b/src/NServiceBus.AcceptanceTesting/Customization/Conventions.cs index f1f5f8b5bac..fec3af36f0a 100644 --- a/src/NServiceBus.AcceptanceTesting/Customization/Conventions.cs +++ b/src/NServiceBus.AcceptanceTesting/Customization/Conventions.cs @@ -10,7 +10,7 @@ static Conventions() EndpointNamingConvention = t => t.Name; } - public static Func DefaultRunDescriptor = () => new RunDescriptor {Key = "Default"}; + public static Func DefaultRunDescriptor = () => new RunDescriptor("Default"); public static Func EndpointNamingConvention { get; set; } } diff --git a/src/NServiceBus.AcceptanceTesting/Customization/EndpointConfigurationExtensions.cs b/src/NServiceBus.AcceptanceTesting/Customization/EndpointConfigurationExtensions.cs new file mode 100644 index 00000000000..92405479ca7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Customization/EndpointConfigurationExtensions.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.AcceptanceTesting.Customization +{ + using System; + using System.Collections.Generic; + + public static class EndpointConfigurationExtensions + { + /// + /// Backdoor into the core types to scan. This allows people to test a subset of functionality when running Acceptance tests + /// + /// The instance to apply the settings to. + /// Override the types to scan. + public static void TypesToIncludeInScan(this EndpointConfiguration config, IEnumerable typesToScan) + { + config.TypesToScanInternal(typesToScan); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/EndpointConfigurationBuilder.cs b/src/NServiceBus.AcceptanceTesting/EndpointConfigurationBuilder.cs index 09b2d548ecb..d8288808071 100644 --- a/src/NServiceBus.AcceptanceTesting/EndpointConfigurationBuilder.cs +++ b/src/NServiceBus.AcceptanceTesting/EndpointConfigurationBuilder.cs @@ -17,7 +17,7 @@ public EndpointConfigurationBuilder AuditTo() return this; } - public EndpointConfigurationBuilder AuditTo(Address addressOfAuditQueue) + public EndpointConfigurationBuilder AuditTo(string addressOfAuditQueue) { configuration.AddressOfAuditQueue = addressOfAuditQueue; return this; @@ -39,12 +39,12 @@ public EndpointConfigurationBuilder CustomEndpointName(string customEndpointName public EndpointConfigurationBuilder AddMapping(Type endpoint) { - configuration.EndpointMappings.Add(typeof(T),endpoint); + configuration.EndpointMappings.Add(typeof(T), endpoint); return this; } - EndpointConfiguration CreateScenario() + EndpointCustomizationConfiguration CreateScenario() { configuration.BuilderType = GetType(); @@ -56,29 +56,49 @@ EndpointConfiguration CreateScenario() return EndpointSetup(c => { }); } - public EndpointConfigurationBuilder EndpointSetup(Action configurationBuilderCustomization = null) where T : IEndpointSetupTemplate, new() + public EndpointConfigurationBuilder EndpointSetup(Action configurationBuilderCustomization) where T : IEndpointSetupTemplate, new() { if (configurationBuilderCustomization == null) { configurationBuilderCustomization = b => { }; } - configuration.GetConfiguration = (settings, routingTable) => + + return EndpointSetup((bc, esc) => + { + configurationBuilderCustomization(bc); + }); + } + + public EndpointConfigurationBuilder EndpointSetup(Action configurationBuilderCustomization) where T : IEndpointSetupTemplate, new() + { + if (configurationBuilderCustomization == null) + { + configurationBuilderCustomization = (rd, b) => { }; + } + configuration.GetConfiguration = async (runDescriptor, routingTable) => + { + var endpointSetupTemplate = new T(); + var scenarioConfigSource = new ScenarioConfigSource(configuration, routingTable); + var endpointConfiguration = await endpointSetupTemplate.GetConfiguration(runDescriptor, configuration, scenarioConfigSource, bc => { - var endpointSetupTemplate = new T(); - var scenarioConfigSource = new ScenarioConfigSource(configuration, routingTable); - return endpointSetupTemplate.GetConfiguration(settings, configuration, scenarioConfigSource, configurationBuilderCustomization); - }; + configurationBuilderCustomization(bc, runDescriptor); + }).ConfigureAwait(false); + + return endpointConfiguration; + }; return this; } - EndpointConfiguration IEndpointConfigurationFactory.Get() + + EndpointCustomizationConfiguration IEndpointConfigurationFactory.Get() { return CreateScenario(); } + public ScenarioContext ScenarioContext { get; set; } + - - readonly EndpointConfiguration configuration = new EndpointConfiguration(); + EndpointCustomizationConfiguration configuration = new EndpointCustomizationConfiguration(); public EndpointConfigurationBuilder WithConfig(Action action) where T : new() { @@ -86,8 +106,8 @@ EndpointConfiguration IEndpointConfigurationFactory.Get() action(config); - configuration.UserDefinedConfigSections[typeof (T)] = config; - + configuration.UserDefinedConfigSections[typeof(T)] = config; + return this; } diff --git a/src/NServiceBus.AcceptanceTesting/NServiceBus.AcceptanceTesting.csproj b/src/NServiceBus.AcceptanceTesting/NServiceBus.AcceptanceTesting.csproj index 95a5db99f3b..8bede4b8544 100644 --- a/src/NServiceBus.AcceptanceTesting/NServiceBus.AcceptanceTesting.csproj +++ b/src/NServiceBus.AcceptanceTesting/NServiceBus.AcceptanceTesting.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,7 +9,7 @@ Properties NServiceBus.AcceptanceTesting NServiceBus.AcceptanceTesting - v4.5 + v4.5.2 512 ..\ @@ -27,8 +27,9 @@ DEBUG;TRACE prompt 4 - true + false false + 436 pdbonly @@ -39,6 +40,7 @@ 4 true false + 436 @@ -48,6 +50,8 @@ + + @@ -55,16 +59,22 @@ + + + - + - - + + + + + @@ -74,6 +84,8 @@ + + @@ -87,11 +99,11 @@ - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - - - + + + \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Scenario.cs b/src/NServiceBus.AcceptanceTesting/Scenario.cs index 7cc159e7303..e00b27efdee 100644 --- a/src/NServiceBus.AcceptanceTesting/Scenario.cs +++ b/src/NServiceBus.AcceptanceTesting/Scenario.cs @@ -7,19 +7,12 @@ public class Scenario { public static IScenarioWithEndpointBehavior Define() where T : ScenarioContext, new() { - Func instance = () => new T(); - return new ScenarioWithContext(instance); + return new ScenarioWithContext(c => { }); } - public static IScenarioWithEndpointBehavior Define(T context) where T : ScenarioContext, new() + public static IScenarioWithEndpointBehavior Define(Action contextInitializer) where T : ScenarioContext, new() { - return new ScenarioWithContext(()=>context); + return new ScenarioWithContext(contextInitializer); } - - public static IScenarioWithEndpointBehavior Define(Func contextFactory) where T : ScenarioContext, new() - { - return new ScenarioWithContext(contextFactory); - } - } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/ScenarioContext.cs b/src/NServiceBus.AcceptanceTesting/ScenarioContext.cs index 66b75be0605..abe4ecef38f 100644 --- a/src/NServiceBus.AcceptanceTesting/ScenarioContext.cs +++ b/src/NServiceBus.AcceptanceTesting/ScenarioContext.cs @@ -1,102 +1,50 @@ namespace NServiceBus.AcceptanceTesting { using System; + using System.Collections.Concurrent; using System.Collections.Generic; - using System.Linq; - using System.Runtime.Remoting.Activation; - using System.Runtime.Remoting.Contexts; - using System.Runtime.Remoting.Messaging; + using Faults; + using Logging; - [Intercept] - [Serializable] - public abstract class ScenarioContext : ContextBoundObject + public class ScenarioContext { - public event EventHandler ContextPropertyChanged; - - [AttributeUsage(AttributeTargets.Class)] - sealed class InterceptAttribute : ContextAttribute, IContributeObjectSink - { - public InterceptAttribute() - : base("InterceptProperty") - { - } - - public override void GetPropertiesForNewContext(IConstructionCallMessage message) - { - message.ContextProperties.Add(this); - } - - public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink) - { - return new InterceptSink { Target = (ScenarioContext)obj, NextSink = nextSink }; - } - } - - class InterceptSink : IMessageSink - { - public IMessageSink NextSink { get; set; } - - public ScenarioContext Target; - - public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink sink) - { - throw new NotSupportedException("AsyncProcessMessage is not supported."); - } - - public IMessage SyncProcessMessage(IMessage msg) - { - var call = msg as IMethodCallMessage; - if (call != null) - { - var method = call.MethodName; - - - if (Target.ContextPropertyChanged != null && method.StartsWith("set")) - { - Target.ContextPropertyChanged(Target, EventArgs.Empty); - } - } - - return NextSink.SyncProcessMessage(msg); - } - } + public Guid TestRunId { get; } = Guid.NewGuid(); public bool EndpointsStarted { get; set; } - public string Exceptions { get; set; } public bool HasNativePubSubSupport { get; set; } - public string Trace { get; set; } + public string Trace => string.Join(Environment.NewLine, traceQueue.ToArray()); public void AddTrace(string trace) { - Trace += DateTime.Now.ToString("HH:mm:ss.ffffff") + " - " + trace + Environment.NewLine; + traceQueue.Enqueue($"{DateTime.Now:HH:mm:ss.ffffff} - {trace}"); } - public void RecordEndpointLog(string endpointName,string level ,string message) - { - endpointLogs.Add(new EndpointLogItem - { - Endpoint = endpointName, - Level = level, - Message = message - }); - } + public ConcurrentDictionary> FailedMessages = new ConcurrentDictionary>(); + public ConcurrentQueue Logs = new ConcurrentQueue(); - public List GetAllLogs() - { - return endpointLogs.ToList(); - } + ConcurrentQueue traceQueue = new ConcurrentQueue(); + internal LogLevel LogLevel { get; set; } = LogLevel.Info; - List endpointLogs = new List(); + internal ConcurrentDictionary UnfinishedFailedMessages = new ConcurrentDictionary(); + + public void SetLogLevel(LogLevel level) + { + LogLevel = level; + } - public class EndpointLogItem + public class LogItem { - public string Endpoint { get; set; } public string Message { get; set; } - public string Level { get; set; } + public LogLevel Level { get; set; } + + public override string ToString() + { + return $"{Level}: {Message}"; + } } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/ScenarioVerification.cs b/src/NServiceBus.AcceptanceTesting/ScenarioVerification.cs index 99ee26ecf5f..0c021f97a14 100644 --- a/src/NServiceBus.AcceptanceTesting/ScenarioVerification.cs +++ b/src/NServiceBus.AcceptanceTesting/ScenarioVerification.cs @@ -9,7 +9,7 @@ public class ScenarioVerification : IScenarioVerification where T : ScenarioC public void Verify(ScenarioContext context) { - Should(((T)context)); + Should((T)context); } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/ScenarioWithContext.cs b/src/NServiceBus.AcceptanceTesting/ScenarioWithContext.cs index 09a3aab5a75..b580ea8aa10 100644 --- a/src/NServiceBus.AcceptanceTesting/ScenarioWithContext.cs +++ b/src/NServiceBus.AcceptanceTesting/ScenarioWithContext.cs @@ -4,49 +4,30 @@ namespace NServiceBus.AcceptanceTesting using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using System.Threading.Tasks; using Customization; using Support; + using Logging; public class ScenarioWithContext : IScenarioWithEndpointBehavior, IAdvancedScenarioWithEndpointBehavior where TContext : ScenarioContext, new() { - public ScenarioWithContext(Func factory) + public ScenarioWithContext(Action initializer) { - contextFactory = factory; + contextInitializer = initializer; } - public IScenarioWithEndpointBehavior WithEndpoint() where T : EndpointConfigurationBuilder - { - return WithEndpoint(b => { }); - } - - public IScenarioWithEndpointBehavior WithEndpoint(Action> defineBehavior) where T : EndpointConfigurationBuilder + public Task> Run(TimeSpan? testExecutionTimeout = null) { + var settings = new RunSettings(); + if (testExecutionTimeout.HasValue) + { + settings.TestExecutionTimeout = testExecutionTimeout.Value; + } - var builder = new EndpointBehaviorBuilder(typeof (T)); - - defineBehavior(builder); - - behaviors.Add(builder.Build()); - - return this; + return Run(settings); } - public IScenarioWithEndpointBehavior Done(Func func) - { - done = c => func((TContext)c); - - return this; - } - - - public IEnumerable Run(TimeSpan? testExecutionTimeout = null) - { - return Run(new RunSettings - { - TestExecutionTimeout = testExecutionTimeout - }); - } - public IEnumerable Run(RunSettings settings) + public async Task> Run(RunSettings settings) { var builder = new RunDescriptorsBuilder(); @@ -56,97 +37,102 @@ public IEnumerable Run(RunSettings settings) if (!runDescriptors.Any()) { - Console.Out.WriteLine("No active rundescriptors was found for this test, test will not be executed"); + Console.WriteLine("No active rundescriptors were found for this test, test will not be executed"); return new List(); } foreach (var runDescriptor in runDescriptors) { - runDescriptor.ScenarioContext = contextFactory(); - runDescriptor.TestExecutionTimeout = settings.TestExecutionTimeout ?? TimeSpan.FromSeconds(90); - runDescriptor.UseSeparateAppdomains = settings.UseSeparateAppDomains; + var scenarioContext = new TContext(); + contextInitializer(scenarioContext); + runDescriptor.ScenarioContext = scenarioContext; + runDescriptor.Settings.Merge(settings); } + LogManager.UseFactory(new ContextAppenderFactory()); + var sw = new Stopwatch(); sw.Start(); - ScenarioRunner.Run(runDescriptors, behaviors, shoulds, done, limitTestParallelismTo, reports, allowedExceptions); - + await ScenarioRunner.Run(runDescriptors, behaviors, shoulds, done, reports).ConfigureAwait(false); sw.Stop(); - Console.Out.WriteLine("Total time for testrun: {0}", sw.Elapsed); + Console.WriteLine("Total time for testrun: {0}", sw.Elapsed); - return runDescriptors.Select(r => (TContext)r.ScenarioContext); + return runDescriptors.Select(r => (TContext) r.ScenarioContext); } - - - public IAdvancedScenarioWithEndpointBehavior Repeat(Action action) + public IAdvancedScenarioWithEndpointBehavior Should(Action should) { - runDescriptorsBuilderAction = action; + shoulds.Add(new ScenarioVerification + { + ContextType = typeof(TContext), + Should = should + }); return this; } - public IScenarioWithEndpointBehavior AllowExceptions(Func filter = null) + public IAdvancedScenarioWithEndpointBehavior Report(Action reportActions) { - if (filter == null) - { - filter = exception => true; - } - - allowedExceptions = filter; + reports = reportActions; return this; } - public IAdvancedScenarioWithEndpointBehavior MaxTestParallelism(int maxParallelism) + public IScenarioWithEndpointBehavior WithEndpoint() where T : EndpointConfigurationBuilder + { + return WithEndpoint(b => { }); + } + + public IScenarioWithEndpointBehavior WithEndpoint(Action> defineBehavior) where T : EndpointConfigurationBuilder { - limitTestParallelismTo = maxParallelism; + var builder = new EndpointBehaviorBuilder(typeof(T)); + + defineBehavior(builder); + + behaviors.Add(builder.Build()); return this; } - - TContext IScenarioWithEndpointBehavior.Run(TimeSpan? testExecutionTimeout) + public IScenarioWithEndpointBehavior Done(Func func) { - return Run(new RunSettings - { - TestExecutionTimeout = testExecutionTimeout - }).Single(); + done = c => func((TContext) c); + + return this; } - TContext IScenarioWithEndpointBehavior.Run(RunSettings settings) + public IAdvancedScenarioWithEndpointBehavior Repeat(Action action) { - return Run(settings).Single(); + runDescriptorsBuilderAction = action; + + return this; } - public IAdvancedScenarioWithEndpointBehavior Should(Action should) + async Task IScenarioWithEndpointBehavior.Run(TimeSpan? testExecutionTimeout) { - shoulds.Add(new ScenarioVerification + var settings = new RunSettings(); + if (testExecutionTimeout.HasValue) { - ContextType = typeof(TContext), - Should = should - }); + settings.TestExecutionTimeout = testExecutionTimeout.Value; + } - return this; + var contexts = await Run(settings).ConfigureAwait(false); + return contexts.Single(); } - - public IAdvancedScenarioWithEndpointBehavior Report(Action reportActions) + async Task IScenarioWithEndpointBehavior.Run(RunSettings settings) { - reports = reportActions; - return this; + var contexts = await Run(settings).ConfigureAwait(false); + return contexts.Single(); } - - int limitTestParallelismTo; - readonly IList behaviors = new List(); - Action runDescriptorsBuilderAction = builder => builder.For(Conventions.DefaultRunDescriptor()); - IList shoulds = new List(); - public Func done = context => true; + List behaviors = new List(); + Action contextInitializer; + Func done = context => true; - Func contextFactory = () => new TContext(); Action reports; - Func allowedExceptions = exception => false; + Action runDescriptorsBuilderAction = builder => builder.For(Conventions.DefaultRunDescriptor()); + List shoulds = new List(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/ActiveRunner.cs b/src/NServiceBus.AcceptanceTesting/Support/ActiveRunner.cs index 47d97701ab8..c5b1fe16f35 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/ActiveRunner.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/ActiveRunner.cs @@ -1,11 +1,8 @@ namespace NServiceBus.AcceptanceTesting.Support { - using System; - class ActiveRunner { public EndpointRunner Instance { get; set; } public string EndpointName { get; set; } - public AppDomain AppDomain { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/ActiveTestExecutionConfigurer.cs b/src/NServiceBus.AcceptanceTesting/Support/ActiveTestExecutionConfigurer.cs new file mode 100644 index 00000000000..768fe820b1e --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/ActiveTestExecutionConfigurer.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System.Collections.Generic; + using System.Linq; + + public class ActiveTestExecutionConfigurer : List + { + public override string ToString() + { + return string.Join("; ", this.Select(t => t.GetType().Name)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/AggregateExceptionTestExtensions.cs b/src/NServiceBus.AcceptanceTesting/Support/AggregateExceptionTestExtensions.cs new file mode 100644 index 00000000000..b7c1361ffc2 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/AggregateExceptionTestExtensions.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + + public static class AggregateExceptionTestExtensions + { + public static MessagesFailedException ExpectFailedMessages(this AggregateException aggregateException) + { + var messagesFailedException = aggregateException.InnerException as MessagesFailedException; + if (messagesFailedException != null) + { + return messagesFailedException; + } + + throw new ArgumentException( + "Expected AggregateException to contain a MessagesFailedException, but it did not.", + nameof(aggregateException), + aggregateException); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/BehaviourFactory.cs b/src/NServiceBus.AcceptanceTesting/Support/BehaviourFactory.cs index 79866d51bb1..000dee27584 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/BehaviourFactory.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/BehaviourFactory.cs @@ -2,6 +2,7 @@ { public interface IEndpointConfigurationFactory { - EndpointConfiguration Get(); + EndpointCustomizationConfiguration Get(); + ScenarioContext ScenarioContext { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/DefaultScenarioDescriptor.cs b/src/NServiceBus.AcceptanceTesting/Support/DefaultScenarioDescriptor.cs index b98231f6afa..da408b7b623 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/DefaultScenarioDescriptor.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/DefaultScenarioDescriptor.cs @@ -4,7 +4,7 @@ public class DefaultScenarioDescriptor : ScenarioDescriptor { public DefaultScenarioDescriptor() { - Add(new RunDescriptor { Key = "Default Scenario" }); + Add(new RunDescriptor("Default Scenario")); } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/EndpointBehavior.cs b/src/NServiceBus.AcceptanceTesting/Support/EndpointBehavior.cs index e442df963b0..d616e00d7a8 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/EndpointBehavior.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/EndpointBehavior.cs @@ -2,87 +2,21 @@ { using System; using System.Collections.Generic; - using System.Diagnostics; - using System.Threading; - [Serializable] - public class EndpointBehavior : MarshalByRefObject + public class EndpointBehavior { public EndpointBehavior(Type builderType) { EndpointBuilderType = builderType; - CustomConfig = new List>(); + CustomConfig = new List>(); } public Type EndpointBuilderType { get; private set; } - public List Givens { get; set; } public List Whens { get; set; } - public List> CustomConfig { get; set; } - public string AppConfig { get; set; } - } - - [Serializable] - public class WhenDefinition : IWhenDefinition where TContext : ScenarioContext - { - public WhenDefinition(Predicate condition, Action action) - { - id = Guid.NewGuid(); - this.condition = condition; - busAction = action; - } - - public WhenDefinition(Predicate condition, Action actionWithContext) - { - id = Guid.NewGuid(); - this.condition = condition; - busAndContextAction = actionWithContext; - } - - public Guid Id { get { return id; } } - - public bool ExecuteAction(ScenarioContext context, IBus bus) - { - var c = context as TContext; - - if (!condition(c)) - { - return false; - } - - - if (busAction != null) - { - busAction(bus); - } - else - { - busAndContextAction(bus, c); - - } - - Debug.WriteLine("Condition {0} has fired - Thread: {1} AppDomain: {2}", id, Thread.CurrentThread.ManagedThreadId,AppDomain.CurrentDomain.FriendlyName); - - return true; - } - - readonly Predicate condition; - readonly Action busAction; - readonly Action busAndContextAction; - Guid id; - } - - public interface IGivenDefinition - { - Action GetAction(ScenarioContext context); - } - - - public interface IWhenDefinition - { - bool ExecuteAction(ScenarioContext context, IBus bus); + public List> CustomConfig { get; set; } - Guid Id { get; } + public bool DoNotFailOnErrorMessages { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/EndpointBehaviorBuilder.cs b/src/NServiceBus.AcceptanceTesting/Support/EndpointBehaviorBuilder.cs index 3769a9294a9..b1dfe273831 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/EndpointBehaviorBuilder.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/EndpointBehaviorBuilder.cs @@ -2,64 +2,59 @@ { using System; using System.Collections.Generic; + using System.Threading.Tasks; - public class EndpointBehaviorBuilder where TContext:ScenarioContext + public class EndpointBehaviorBuilder where TContext : ScenarioContext { - public EndpointBehaviorBuilder(Type type) { behavior = new EndpointBehavior(type) - { - Givens = new List(), - Whens = new List() - }; + { + Whens = new List() + }; } - - public EndpointBehaviorBuilder Given(Action action) + public EndpointBehaviorBuilder When(Func action) { - behavior.Givens.Add(new GivenDefinition(action)); - - return this; + return When(c => true, action); } - - public EndpointBehaviorBuilder Given(Action action) + public EndpointBehaviorBuilder When(Func action) { - behavior.Givens.Add(new GivenDefinition(action)); - - return this; + return When(c => true, action); } - public EndpointBehaviorBuilder When(Action action) + public EndpointBehaviorBuilder When(Predicate condition, Func action) { - return When(c => true, action); + behavior.Whens.Add(new WhenDefinition(condition, action)); + + return this; } - public EndpointBehaviorBuilder When(Predicate condition, Action action) + public EndpointBehaviorBuilder When(Predicate condition, Func action) { - behavior.Whens.Add(new WhenDefinition(condition,action)); + behavior.Whens.Add(new WhenDefinition(condition, action)); return this; } - public EndpointBehaviorBuilder When(Predicate condition, Action action) + public EndpointBehaviorBuilder CustomConfig(Action action) { - behavior.Whens.Add(new WhenDefinition(condition,action)); + behavior.CustomConfig.Add((busConfig, context) => action(busConfig)); return this; } - public EndpointBehaviorBuilder CustomConfig(Action action) + public EndpointBehaviorBuilder CustomConfig(Action action) { - behavior.CustomConfig.Add(action); + behavior.CustomConfig.Add((configuration, context) => action(configuration, (TContext) context)); return this; } - public EndpointBehaviorBuilder AppConfig(string appConfig) + public EndpointBehaviorBuilder DoNotFailOnErrorMessages() { - behavior.AppConfig = appConfig; + behavior.DoNotFailOnErrorMessages = true; return this; } @@ -69,6 +64,6 @@ public EndpointBehavior Build() return behavior; } - readonly EndpointBehavior behavior; + EndpointBehavior behavior; } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/EndpointConfiguration.cs b/src/NServiceBus.AcceptanceTesting/Support/EndpointConfiguration.cs deleted file mode 100644 index ee2d61e1a24..00000000000 --- a/src/NServiceBus.AcceptanceTesting/Support/EndpointConfiguration.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.AcceptanceTesting.Support -{ - using System; - using System.Collections.Generic; - - public class EndpointConfiguration - { - public EndpointConfiguration() - { - UserDefinedConfigSections = new Dictionary(); - TypesToExclude = new List(); - TypesToInclude = new List(); - } - - public IDictionary EndpointMappings { get; set; } - - public IList TypesToExclude { get; set; } - - public IList TypesToInclude { get; set; } - - public Func, BusConfiguration> GetConfiguration { get; set; } - - public string EndpointName - { - get - { - if (!string.IsNullOrEmpty(CustomEndpointName)) - return CustomEndpointName; - return endpointName; - } - set { endpointName = value; } - } - - public Type BuilderType { get; set; } - - public Address AddressOfAuditQueue { get; set; } - - public IDictionary UserDefinedConfigSections { get; private set; } - - public string CustomMachineName { get; set; } - - public string CustomEndpointName { get; set; } - - public Type AuditEndpoint { get; set; } - public bool SendOnly { get; set; } - - string endpointName; - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/EndpointCustomizationConfiguration.cs b/src/NServiceBus.AcceptanceTesting/Support/EndpointCustomizationConfiguration.cs new file mode 100644 index 00000000000..e6636cba63c --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/EndpointCustomizationConfiguration.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + public class EndpointCustomizationConfiguration + { + public EndpointCustomizationConfiguration() + { + UserDefinedConfigSections = new Dictionary(); + TypesToExclude = new List(); + TypesToInclude = new List(); + } + + public IDictionary EndpointMappings { get; set; } + + public IList TypesToExclude { get; set; } + + public IList TypesToInclude { get; set; } + + public Func, Task> GetConfiguration { get; set; } + + public string EndpointName + { + get + { + if (!string.IsNullOrEmpty(CustomEndpointName)) + return CustomEndpointName; + return endpointName; + } + set { endpointName = value; } + } + + public Type BuilderType { get; set; } + + public string AddressOfAuditQueue { get; set; } + + public IDictionary UserDefinedConfigSections { get; private set; } + + public string CustomMachineName { get; set; } + + public string CustomEndpointName { get; set; } + + public Type AuditEndpoint { get; set; } + public bool SendOnly { get; set; } + + string endpointName; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/EndpointRunner.cs b/src/NServiceBus.AcceptanceTesting/Support/EndpointRunner.cs index fa118b77afd..adf8a3fb2b5 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/EndpointRunner.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/EndpointRunner.cs @@ -2,40 +2,38 @@ { using System; using System.Collections.Generic; - using System.Runtime.Remoting.Lifetime; + using System.Linq; using System.Threading; using System.Threading.Tasks; + using Configuration.AdvanceExtensibility; using Logging; using NServiceBus.Support; - using NServiceBus.Unicast; - using Transports; + using Transport; - [Serializable] - public class EndpointRunner : MarshalByRefObject + public class EndpointRunner { static ILog Logger = LogManager.GetLogger(); - readonly SemaphoreSlim contextChanged = new SemaphoreSlim(0); - readonly IList executedWhens = new List(); EndpointBehavior behavior; - IStartableBus bus; - ISendOnlyBus sendOnlyBus; - EndpointConfiguration configuration; - Task executeWhens; + IStartableEndpoint startable; + IEndpointInstance endpointInstance; + EndpointCustomizationConfiguration configuration; ScenarioContext scenarioContext; - bool stopped; - RunDescriptor runDescriptor; + RunSettings runSettings; + EndpointConfiguration endpointConfiguration; - public Result Initialize(RunDescriptor run, EndpointBehavior endpointBehavior, + public bool FailOnErrorMessage => !behavior.DoNotFailOnErrorMessages; + + public async Task Initialize(RunDescriptor run, EndpointBehavior endpointBehavior, IDictionary routingTable, string endpointName) { try { - runDescriptor = run; behavior = endpointBehavior; scenarioContext = run.ScenarioContext; - configuration = - ((IEndpointConfigurationFactory)Activator.CreateInstance(endpointBehavior.EndpointBuilderType)) - .Get(); + runSettings = run.Settings; + var endpointConfigurationFactory = (IEndpointConfigurationFactory)Activator.CreateInstance(endpointBehavior.EndpointBuilderType); + endpointConfigurationFactory.ScenarioContext = run.ScenarioContext; + configuration = endpointConfigurationFactory.Get(); configuration.EndpointName = endpointName; if (!string.IsNullOrEmpty(configuration.CustomMachineName)) @@ -44,183 +42,148 @@ public Result Initialize(RunDescriptor run, EndpointBehavior endpointBehavior, } //apply custom config settings - var busConfiguration = configuration.GetConfiguration(run, routingTable); - - scenarioContext.ContextPropertyChanged += scenarioContext_ContextPropertyChanged; + if (configuration.GetConfiguration == null) + { + throw new Exception($"Missing EndpointSetup in the constructor of {endpointName} endpoint."); + } + endpointConfiguration = await configuration.GetConfiguration(run, routingTable).ConfigureAwait(false); + RegisterInheritanceHierarchyOfContextInSettings(scenarioContext); - endpointBehavior.CustomConfig.ForEach(customAction => customAction(busConfiguration)); + endpointBehavior.CustomConfig.ForEach(customAction => customAction(endpointConfiguration, scenarioContext)); if (configuration.SendOnly) { - sendOnlyBus = Bus.CreateSendOnly(busConfiguration); + endpointConfiguration.SendOnly(); } - else - { - bus = Bus.Create(busConfiguration); - var transportDefinition = ((UnicastBus)bus).Settings.Get(); - scenarioContext.HasNativePubSubSupport = transportDefinition.HasNativePubSubSupport; - } + startable = await Endpoint.Create(endpointConfiguration).ConfigureAwait(false); - - executeWhens = Task.Factory.StartNew(() => + if (!configuration.SendOnly) { - while (!stopped) - { - //we spin around each 5s since the callback mechanism seems to be shaky - contextChanged.Wait(TimeSpan.FromSeconds(5)); - - lock (behavior) - { - foreach (var when in behavior.Whens) - { - if (executedWhens.Contains(when.Id)) - { - continue; - } - - if (when.ExecuteAction(scenarioContext, bus)) - { - executedWhens.Add(when.Id); - } - } - } - } - }); - - return Result.Success(); + var transportInfrastructure = endpointConfiguration.GetSettings().Get(); + scenarioContext.HasNativePubSubSupport = transportInfrastructure.OutboundRoutingPolicy.Publishes == OutboundRoutingType.Multicast; + } } catch (Exception ex) { Logger.Error("Failed to initialize endpoint " + endpointName, ex); - return Result.Failure(ex); + throw; } } - private void scenarioContext_ContextPropertyChanged(object sender, EventArgs e) + void RegisterInheritanceHierarchyOfContextInSettings(ScenarioContext context) { - contextChanged.Release(); + var type = context.GetType(); + while (type != typeof(object)) + { + endpointConfiguration.GetSettings().Set(type.FullName, scenarioContext); + type = type.BaseType; + } } - public Result Start() + public async Task Start(CancellationToken token) { try { - foreach (var given in behavior.Givens) - { - var action = given.GetAction(scenarioContext); - - if (configuration.SendOnly) - { - action(new IBusAdapter(sendOnlyBus)); - } - else - { - - action(bus); - } - } + endpointInstance = await startable.Start().ConfigureAwait(false); - if (!configuration.SendOnly) + if (token.IsCancellationRequested) { - bus.Start(); + throw new OperationCanceledException("Endpoint start was aborted"); } - - return Result.Success(); } catch (Exception ex) { Logger.Error("Failed to start endpoint " + configuration.EndpointName, ex); - return Result.Failure(ex); + throw; } } - public Result Stop() + public async Task Whens(CancellationToken token) { try { - stopped = true; + if (behavior.Whens.Count != 0) + { + await Task.Run(async () => + { + var executedWhens = new List(); - scenarioContext.ContextPropertyChanged -= scenarioContext_ContextPropertyChanged; + while (!token.IsCancellationRequested) + { + if (executedWhens.Count == behavior.Whens.Count) + { + break; + } - executeWhens.Wait(); - contextChanged.Dispose(); + if (token.IsCancellationRequested) + { + break; + } - if (configuration.SendOnly) - { - sendOnlyBus.Dispose(); - } - else - { - bus.Dispose(); + foreach (var when in behavior.Whens) + { + if (token.IsCancellationRequested) + { + break; + } - } + if (executedWhens.Contains(when.Id)) + { + continue; + } - return Result.Success(); + if (await when.ExecuteAction(scenarioContext, endpointInstance).ConfigureAwait(false)) + { + executedWhens.Add(when.Id); + } + } + } + }, token).ConfigureAwait(false); + } } catch (Exception ex) { - Logger.Error("Failed to stop endpoint " + configuration.EndpointName, ex); + Logger.Error($"Failed to execute Whens on endpoint{configuration.EndpointName}", ex); - return Result.Failure(ex); + throw; } } - public string Name() + public async Task Stop() { - if (runDescriptor.UseSeparateAppdomains) + try { - return AppDomain.CurrentDomain.FriendlyName; + await endpointInstance.Stop().ConfigureAwait(false); } - - return configuration.EndpointName; - } - - public override object InitializeLifetimeService() - { - var lease = (ILease)base.InitializeLifetimeService(); - if (lease.CurrentState == LeaseState.Initial) + catch (Exception ex) { - lease.InitialLeaseTime = TimeSpan.FromMinutes(2); - lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); - lease.RenewOnCallTime = TimeSpan.FromSeconds(2); + Logger.Error("Failed to stop endpoint " + configuration.EndpointName, ex); + throw; } - return lease; - } - - [Serializable] - public class Result - { - public Exception Exception { get; set; } - - public bool Failed + finally { - get { return Exception != null; } + await Cleanup().ConfigureAwait(false); } + } - public static Result Success() + Task Cleanup() + { + ActiveTestExecutionConfigurer cleaners; + var cleanersKey = "ConfigureTestExecution." + configuration.EndpointName; + if (runSettings.TryGet(cleanersKey, out cleaners)) { - return new Result(); + var tasks = cleaners.Select(cleaner => cleaner.Cleanup()); + return Task.WhenAll(tasks); } - public static Result Failure(Exception ex) - { - var baseException = ex.GetBaseException(); - - if (ex.GetType().IsSerializable) - { - return new Result - { - Exception = baseException - }; - } + return Task.FromResult(0); + } - return new Result - { - Exception = new Exception(baseException.Message) - }; - } + public string Name() + { + return configuration.EndpointName; } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/FailTestOnErrorMessageFeature.cs b/src/NServiceBus.AcceptanceTesting/Support/FailTestOnErrorMessageFeature.cs new file mode 100644 index 00000000000..07c093c0c64 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/FailTestOnErrorMessageFeature.cs @@ -0,0 +1,65 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Threading.Tasks; + using Faults; + using Features; + using Pipeline; + + public class FailTestOnErrorMessageFeature : Feature + { + public FailTestOnErrorMessageFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var scenarioContext = context.Settings.Get(); + + context.Pipeline.Register(new CaptureExceptionBehavior(scenarioContext.UnfinishedFailedMessages), "Captures unhandled exceptions from processed messages for the AcceptanceTesting Framework"); + + context.Settings.Get().Subscribe(m => + { + scenarioContext.FailedMessages.AddOrUpdate( + context.Settings.EndpointName(), + new[] + { + new FailedMessage(m.Message.MessageId, m.Message.Headers, m.Message.Body, m.Exception, m.ErrorQueue) + }, + (i, failed) => + { + var result = failed.ToList(); + result.Add(new FailedMessage(m.Message.MessageId, m.Message.Headers, m.Message.Body, m.Exception, m.ErrorQueue)); + return result; + }); + + //We need to set the error flag to false as we want to reset all processing exceptions caused by immediate retries + scenarioContext.UnfinishedFailedMessages.AddOrUpdate(m.Message.MessageId, id => false, (id, value) => false); + + return Task.FromResult(0); + }); + } + + class CaptureExceptionBehavior : IBehavior + { + public CaptureExceptionBehavior(ConcurrentDictionary failedMessages) + { + this.failedMessages = failedMessages; + } + + public async Task Invoke(ITransportReceiveContext context, Func next) + { + failedMessages.AddOrUpdate(context.Message.MessageId, id => true, (id, value) => true); + + await next(context).ConfigureAwait(false); + + failedMessages.AddOrUpdate(context.Message.MessageId, id => false, (id, value) => false); + } + + ConcurrentDictionary failedMessages; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/GivenDefinition.cs b/src/NServiceBus.AcceptanceTesting/Support/GivenDefinition.cs deleted file mode 100644 index 090c67f649d..00000000000 --- a/src/NServiceBus.AcceptanceTesting/Support/GivenDefinition.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.AcceptanceTesting.Support -{ - using System; - - [Serializable] - public class GivenDefinition : IGivenDefinition where TContext : ScenarioContext - { - public GivenDefinition(Action action) - { - givenAction2 = action; - } - - public GivenDefinition(Action action) - { - givenAction = action; - } - - public Action GetAction(ScenarioContext context) - { - if (givenAction2 != null) - return bus => givenAction2(bus); - - return bus => givenAction(bus, (TContext)context); - } - - readonly Action givenAction; - readonly Action givenAction2; - - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/IBusAdapter.cs b/src/NServiceBus.AcceptanceTesting/Support/IBusAdapter.cs deleted file mode 100644 index 5aef00075c2..00000000000 --- a/src/NServiceBus.AcceptanceTesting/Support/IBusAdapter.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace NServiceBus.AcceptanceTesting.Support -{ - using System; - using System.Collections.Generic; - - public class IBusAdapter : IBus - { - readonly ISendOnlyBus sendOnlyBus; - - public IBusAdapter(ISendOnlyBus sendOnlyBus) - { - this.sendOnlyBus = sendOnlyBus; - } - - public void Dispose() - { - sendOnlyBus.Dispose(); - } - - public void Publish(T message) - { - sendOnlyBus.Publish(message); - } - - public void Publish() - { - sendOnlyBus.Publish(); - } - - public void Publish(Action messageConstructor) - { - sendOnlyBus.Publish(messageConstructor); - } - - public ICallback Send(object message) - { - return sendOnlyBus.Send(message); - } - - public ICallback Send(Action messageConstructor) - { - return sendOnlyBus.Send(messageConstructor); - } - - public ICallback Send(string destination, object message) - { - return sendOnlyBus.Send(destination, message); - } - - public ICallback Send(Address address, object message) - { - return sendOnlyBus.Send(address, message); - } - - public ICallback Send(string destination, Action messageConstructor) - { - return sendOnlyBus.Send(destination, messageConstructor); - } - - public ICallback Send(Address address, Action messageConstructor) - { - return sendOnlyBus.Send(address, messageConstructor); - } - - public ICallback Send(string destination, string correlationId, object message) - { - return sendOnlyBus.Send(destination, correlationId, message); - } - - public ICallback Send(Address address, string correlationId, object message) - { - return sendOnlyBus.Send(address, correlationId, message); - } - - public ICallback Send(string destination, string correlationId, Action messageConstructor) - { - return sendOnlyBus.Send(destination, correlationId, messageConstructor); - } - - public ICallback Send(Address address, string correlationId, Action messageConstructor) - { - return sendOnlyBus.Send(address, correlationId, messageConstructor); - } - - public IDictionary OutgoingHeaders { get; private set; } - - public void Subscribe(Type messageType) - { - throw new NotImplementedException(); - } - - public void Subscribe() - { - throw new NotImplementedException(); - } - - public void Unsubscribe(Type messageType) - { - throw new NotImplementedException(); - } - - public void Unsubscribe() - { - throw new NotImplementedException(); - } - - public ICallback SendLocal(object message) - { - throw new NotImplementedException(); - } - - public ICallback SendLocal(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Defer(TimeSpan delay, object message) - { - throw new NotImplementedException(); - } - - public ICallback Defer(DateTime processAt, object message) - { - throw new NotImplementedException(); - } - - public void Reply(object message) - { - throw new NotImplementedException(); - } - - public void Reply(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public void Return(T errorEnum) - { - throw new NotImplementedException(); - } - - public void HandleCurrentMessageLater() - { - throw new NotImplementedException(); - } - - public void ForwardCurrentMessageTo(string destination) - { - throw new NotImplementedException(); - } - - public void DoNotContinueDispatchingCurrentMessageToHandlers() - { - throw new NotImplementedException(); - } - - public IMessageContext CurrentMessageContext { get; private set; } - -#pragma warning disable 0618 - public IInMemoryOperations InMemory { get; private set; } -#pragma warning restore 0618 - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/IConfigureEndpointTestExecution.cs b/src/NServiceBus.AcceptanceTesting/Support/IConfigureEndpointTestExecution.cs new file mode 100644 index 00000000000..223528102f5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/IConfigureEndpointTestExecution.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System.Threading.Tasks; + + /// + /// Provide a mechanism in acceptance tests for transports and persistences + /// to configure an endpoint for a test and then clean up afterwards. + /// + public interface IConfigureEndpointTestExecution + { + /// + /// Gives the transport/persistence a chance to configure before the test starts. + /// + /// The endpoint name. + /// The EndpointConfiguration instance. + /// Settings from the RunDescriptor specifying Transport, Persistence, + /// connection strings, Serializer, Builder, and other details. Transports must call configuration.UseTransport<T>(). + /// Persistence must call configuration.UsePersistence<T>(). + /// An async Task. + Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings); + + /// + /// Gives the transport/persistence a chance to clean up after the test is complete. Implementations of this class may store + /// private variables during Configure to use during the cleanup phase. + /// + /// An async Task. + Task Cleanup(); + } +} diff --git a/src/NServiceBus.AcceptanceTesting/Support/IConfigureSupportedScenariosForTestExecution.cs b/src/NServiceBus.AcceptanceTesting/Support/IConfigureSupportedScenariosForTestExecution.cs new file mode 100644 index 00000000000..7ddd2ef3ea6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/IConfigureSupportedScenariosForTestExecution.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Collections.Generic; + + /// + /// Configures the supported scenarios for a test execution + /// + public interface IConfigureSupportedScenariosForTestExecution + { + /// + /// Scenario descriptors not supported for this test execution + /// + IEnumerable UnsupportedScenarioDescriptorTypes { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/IEndpointSetupTemplate.cs b/src/NServiceBus.AcceptanceTesting/Support/IEndpointSetupTemplate.cs index a70d58c76f1..4bd5a0c44af 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/IEndpointSetupTemplate.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/IEndpointSetupTemplate.cs @@ -1,10 +1,11 @@ namespace NServiceBus.AcceptanceTesting.Support { using System; + using System.Threading.Tasks; using Config.ConfigurationSource; public interface IEndpointSetupTemplate { - BusConfiguration GetConfiguration(RunDescriptor runDescriptor, EndpointConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization); + Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/IScenarioWithEndpointBehavior.cs b/src/NServiceBus.AcceptanceTesting/Support/IScenarioWithEndpointBehavior.cs index 984e22dd7d5..34bba56d990 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/IScenarioWithEndpointBehavior.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/IScenarioWithEndpointBehavior.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Threading.Tasks; public interface IScenarioWithEndpointBehavior where TContext : ScenarioContext { @@ -11,12 +12,10 @@ public interface IScenarioWithEndpointBehavior where TContext : Scenar IScenarioWithEndpointBehavior Done(Func func); - TContext Run(TimeSpan? testExecutionTimeout = null); - TContext Run(RunSettings settings); + Task Run(TimeSpan? testExecutionTimeout = null); + Task Run(RunSettings settings); IAdvancedScenarioWithEndpointBehavior Repeat(Action runtimeDescriptor); - - IScenarioWithEndpointBehavior AllowExceptions(Func filter = null); } public interface IAdvancedScenarioWithEndpointBehavior where TContext : ScenarioContext @@ -25,11 +24,8 @@ public interface IAdvancedScenarioWithEndpointBehavior where TContext IAdvancedScenarioWithEndpointBehavior Report(Action summaries); + Task> Run(TimeSpan? testExecutionTimeout = null); - IAdvancedScenarioWithEndpointBehavior MaxTestParallelism(int maxParallelism); - - IEnumerable Run(TimeSpan? testExecutionTimeout = null); - - IEnumerable Run(RunSettings settings); + Task> Run(RunSettings settings); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/IWhenDefinition.cs b/src/NServiceBus.AcceptanceTesting/Support/IWhenDefinition.cs new file mode 100644 index 00000000000..7991d386bac --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/IWhenDefinition.cs @@ -0,0 +1,12 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Threading.Tasks; + + public interface IWhenDefinition + { + Task ExecuteAction(ScenarioContext context, IMessageSession session); + + Guid Id { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/MessagesFailedException.cs b/src/NServiceBus.AcceptanceTesting/Support/MessagesFailedException.cs new file mode 100644 index 00000000000..b9c34d21be7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/MessagesFailedException.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using Faults; + + public class MessagesFailedException : Exception + { + public MessagesFailedException(IList failedMessages, ScenarioContext scenarioContext) : base("One or more messages have been moved to the error queue.") + { + ScenarioContext = scenarioContext; + FailedMessages = new ReadOnlyCollection(failedMessages); + } + + public ScenarioContext ScenarioContext { get; } + + public IReadOnlyCollection FailedMessages { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/RunDescriptor.cs b/src/NServiceBus.AcceptanceTesting/Support/RunDescriptor.cs index 6883409248f..ae44e52f9fa 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/RunDescriptor.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/RunDescriptor.cs @@ -1,62 +1,60 @@ namespace NServiceBus.AcceptanceTesting.Support { - using System; - using System.Collections.Generic; - using System.Linq; - - [Serializable] public class RunDescriptor { - protected bool Equals(RunDescriptor other) - { - return string.Equals(Key, other.Key); - } - - public override bool Equals(object obj) + public RunDescriptor(string key) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((RunDescriptor) obj); - } - - public override int GetHashCode() - { - return (Key != null ? Key.GetHashCode() : 0); - } - - public RunDescriptor() - { - Settings = new Dictionary(); + Key = key; + Settings = new RunSettings(); } public RunDescriptor(RunDescriptor template) { - Settings = template.Settings.ToDictionary(entry => entry.Key, - entry => entry.Value); + Settings = new RunSettings(); + Settings.Merge(template.Settings); Key = template.Key; } - public string Key { get; set; } + public string Key { get; private set; } - public IDictionary Settings { get; set; } + public RunSettings Settings { get; } public ScenarioContext ScenarioContext { get; set; } - public TimeSpan TestExecutionTimeout { get; set; } - public int Permutation { get; set; } - public bool UseSeparateAppdomains { get; set; } + protected bool Equals(RunDescriptor other) + { + return string.Equals(Key, other.Key); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) + { + return false; + } + return Equals((RunDescriptor) obj); + } + + public override int GetHashCode() + { + return Key?.GetHashCode() ?? 0; + } public void Merge(RunDescriptor descriptorToAdd) { Key += "." + descriptorToAdd.Key; - foreach (var setting in descriptorToAdd.Settings) - { - Settings[setting.Key] = setting.Value; - } + Settings.Merge(descriptorToAdd.Settings); } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/RunDescriptorsBuilder.cs b/src/NServiceBus.AcceptanceTesting/Support/RunDescriptorsBuilder.cs index 5aa8b743c38..ce382897197 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/RunDescriptorsBuilder.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/RunDescriptorsBuilder.cs @@ -8,7 +8,7 @@ public class RunDescriptorsBuilder { IList descriptors = new List(); - readonly List excludes = new List(); + List excludes = new List(); public RunDescriptorsBuilder For(params RunDescriptor[] runDescriptorsToExclude) where T : ScenarioDescriptor, new() { excludes.AddRange(runDescriptorsToExclude @@ -48,13 +48,12 @@ public RunDescriptorsBuilder For(params RunDescriptor[] descriptorsToAdd) } } - descriptors = result; return this; } - public IList Build() + public List Build() { //if we have found a empty permutation this means that we shouldn't run any permutations. This happens when a test is specified to run for a given key // but that key is not available. Eg running tests for sql server but the sql transport isn't available diff --git a/src/NServiceBus.AcceptanceTesting/Support/RunSettings.cs b/src/NServiceBus.AcceptanceTesting/Support/RunSettings.cs index 9c6fa59ccdc..3246977ae0e 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/RunSettings.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/RunSettings.cs @@ -1,10 +1,174 @@ namespace NServiceBus.AcceptanceTesting.Support { using System; + using System.Collections; + using System.Collections.Concurrent; + using System.Collections.Generic; - public class RunSettings + public class RunSettings : IEnumerable> { - public TimeSpan? TestExecutionTimeout; - public bool UseSeparateAppDomains; + public TimeSpan? TestExecutionTimeout + { + get + { + TimeSpan? timeout; + TryGet("TestExecutionTimeout", out timeout); + return timeout; + } + set + { + Guard.AgainstNull(nameof(value), value); + Set("TestExecutionTimeout", value); + } + } + + public IEnumerator> GetEnumerator() + { + return stash.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Retrieves the specified type from the settings. + /// + /// The type to retrieve. + /// The type instance. + public T Get() + { + return Get(typeof(T).FullName); + } + + /// + /// Retrieves the specified type from the settings + /// + /// The type to retrieve. + /// The key to retrieve the type. + /// The type instance. + public T Get(string key) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + T result; + + if (!TryGet(key, out result)) + { + throw new KeyNotFoundException("No item found in behavior settings with key: " + key); + } + + return result; + } + + /// + /// Gets the requested extension, a new one will be created if needed. + /// + public T GetOrCreate() where T : class, new() + { + T value; + + if (TryGet(out value)) + { + return value; + } + + var newInstance = new T(); + + Set(newInstance); + + return newInstance; + } + + /// + /// Tries to retrieves the specified type from the settings. + /// + /// The type to retrieve. + /// The type instance. + /// true if found, otherwise false. + public bool TryGet(out T result) + { + return TryGet(typeof(T).FullName, out result); + } + + /// + /// Stores the type instance in the settings. + /// + /// The type to store. + /// The instance type to store. + public void Set(T t) + { + Set(typeof(T).FullName, t); + } + + /// + /// Removes the instance type from the settings. + /// + /// The type to remove. + public void Remove() + { + Remove(typeof(T).FullName); + } + + /// + /// Removes the instance type from the settings. + /// + /// The key of the value being removed. + public void Remove(string key) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + object value; + stash.TryRemove(key, out value); + } + + /// + /// Stores the passed instance in the settings. + /// + public void Set(string key, T t) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + stash[key] = t; + } + + /// + /// Tries to retrieves the specified type from the settings. + /// + /// The type to retrieve. + /// The key of the value being looked up. + /// The type instance. + /// true if found, otherwise false. + public bool TryGet(string key, out T result) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + object value; + if (stash.TryGetValue(key, out value)) + { + result = (T) value; + return true; + } + + if (typeof(T).IsValueType) + { + result = default(T); + return false; + } + + result = default(T); + return false; + } + + /// + /// Merges the passed settings into this one. + /// + /// The source settings. + public void Merge(RunSettings settings) + { + foreach (var kvp in settings.stash) + { + stash[kvp.Key] = kvp.Value; + } + } + + ConcurrentDictionary stash = new ConcurrentDictionary(); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/ScenarioConfigSource.cs b/src/NServiceBus.AcceptanceTesting/Support/ScenarioConfigSource.cs index 8b5d64035eb..9b452ee3c3f 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/ScenarioConfigSource.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/ScenarioConfigSource.cs @@ -8,10 +8,10 @@ public class ScenarioConfigSource : IConfigurationSource { - readonly EndpointConfiguration configuration; - readonly IDictionary routingTable; + EndpointCustomizationConfiguration configuration; + IDictionary routingTable; - public ScenarioConfigSource(EndpointConfiguration configuration, IDictionary routingTable) + public ScenarioConfigSource(EndpointCustomizationConfiguration configuration, IDictionary routingTable) { this.configuration = configuration; this.routingTable = routingTable; @@ -55,11 +55,16 @@ public ScenarioConfigSource(EndpointConfiguration configuration, IDictionary() method is called in the test."); + } + return new AuditConfig { QueueName = routingTable[configuration.AuditEndpoint] } as T; } } diff --git a/src/NServiceBus.AcceptanceTesting/Support/ScenarioRunner.cs b/src/NServiceBus.AcceptanceTesting/Support/ScenarioRunner.cs index a9d1fd16d8d..a2587e9decb 100644 --- a/src/NServiceBus.AcceptanceTesting/Support/ScenarioRunner.cs +++ b/src/NServiceBus.AcceptanceTesting/Support/ScenarioRunner.cs @@ -1,14 +1,12 @@ -using System.Runtime.Remoting; -using System.Runtime.Remoting.Lifetime; - -namespace NServiceBus.AcceptanceTesting.Support +namespace NServiceBus.AcceptanceTesting.Support { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; + using System.Globalization; using System.Linq; - using System.Reflection; + using System.Runtime.ExceptionServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,45 +14,23 @@ namespace NServiceBus.AcceptanceTesting.Support public class ScenarioRunner { - public static IEnumerable Run(IList runDescriptors, IList behaviorDescriptors, IList shoulds, Func done, int limitTestParallelismTo, Action reports, Func allowedExceptions) + public static async Task Run(List runDescriptors, List behaviorDescriptors, List shoulds, Func done, Action reports) { - var totalRuns = runDescriptors.Count(); - - var cts = new CancellationTokenSource(); - - var po = new ParallelOptions - { - CancellationToken = cts.Token - }; - - var maxParallelismSetting = Environment.GetEnvironmentVariable("max_test_parallelism"); - int maxParallelism; - if (int.TryParse(maxParallelismSetting, out maxParallelism)) - { - Console.Out.WriteLine("Parallelism limited to: {0}", maxParallelism); - - po.MaxDegreeOfParallelism = maxParallelism; - } - - if (limitTestParallelismTo > 0) - po.MaxDegreeOfParallelism = limitTestParallelismTo; + var totalRuns = runDescriptors.Count; var results = new ConcurrentBag(); try { - Parallel.ForEach(runDescriptors, po, runDescriptor => + foreach (var runDescriptor in runDescriptors) { - if (po.CancellationToken.IsCancellationRequested) - { - return; - } - - Console.Out.WriteLine("{0} - Started @ {1}", runDescriptor.Key, DateTime.Now.ToString()); + Console.WriteLine("{0} - Started @ {1}", runDescriptor.Key, DateTime.Now.ToString(CultureInfo.InvariantCulture)); - var runResult = PerformTestRun(behaviorDescriptors, shoulds, runDescriptor, done, allowedExceptions); + ContextAppenderFactory.SetContext(runDescriptor.ScenarioContext); + var runResult = await PerformTestRun(behaviorDescriptors, shoulds, runDescriptor, done).ConfigureAwait(false); + ContextAppenderFactory.SetContext(null); - Console.Out.WriteLine("{0} - Finished @ {1}", runDescriptor.Key, DateTime.Now.ToString()); + Console.WriteLine("{0} - Finished @ {1}", runDescriptor.Key, DateTime.Now.ToString(CultureInfo.InvariantCulture)); results.Add(new RunSummary { @@ -65,13 +41,13 @@ public static IEnumerable Run(IList runDescriptors, I if (runResult.Failed) { - cts.Cancel(); + break; } - }); + } } catch (OperationCanceledException) { - Console.Out.WriteLine("Test run aborted due to test failures"); + Console.WriteLine("Test run aborted due to test failures"); } var failedRuns = results.Where(s => s.Result.Failed).ToList(); @@ -82,17 +58,16 @@ public static IEnumerable Run(IList runDescriptors, I } if (failedRuns.Any()) - throw new AggregateException("Test run failed due to one or more exception", failedRuns.Select(f => f.Result.Exception)); + { + throw new AggregateException("Test run failed due to one or more exceptions", failedRuns.Select(f => f.Result.Exception)).Flatten(); + } foreach (var runSummary in results.Where(s => !s.Result.Failed)) { DisplayRunResult(runSummary, totalRuns); - if (reports != null) - reports(runSummary); + reports?.Invoke(runSummary); } - - return results; } static void DisplayRunResult(RunSummary summary, int totalRuns) @@ -100,50 +75,54 @@ static void DisplayRunResult(RunSummary summary, int totalRuns) var runDescriptor = summary.RunDescriptor; var runResult = summary.Result; - Console.Out.WriteLine("------------------------------------------------------"); - Console.Out.WriteLine("Test summary for: {0}", runDescriptor.Key); + Console.WriteLine("------------------------------------------------------"); + Console.WriteLine("Test summary for: {0}", runDescriptor.Key); if (totalRuns > 1) - Console.Out.WriteLine(" - Permutation: {0}({1})", runDescriptor.Permutation, totalRuns); - Console.Out.WriteLine(""); + { + Console.WriteLine(" - Permutation: {0}({1})", runDescriptor.Permutation, totalRuns); + } + Console.WriteLine(); PrintSettings(runDescriptor.Settings); - Console.WriteLine(""); + Console.WriteLine(); Console.WriteLine("Endpoints:"); foreach (var endpoint in runResult.ActiveEndpoints) { - Console.Out.WriteLine(" - {0}", endpoint); + Console.WriteLine(" - {0}", endpoint); } if (runResult.Failed) { - Console.Out.WriteLine("Test failed: {0}", runResult.Exception); + Console.WriteLine("Test failed: {0}", runResult.Exception); } else { - Console.Out.WriteLine("Result: Successful - Duration: {0}", runResult.TotalTime); + Console.WriteLine("Result: Successful - Duration: {0}", runResult.TotalTime); } //dump trace and context regardless since asserts outside the should could still fail the test - Console.WriteLine(""); - Console.Out.WriteLine("Context:"); + Console.WriteLine(); + Console.WriteLine("Context:"); foreach (var prop in runResult.ScenarioContext.GetType().GetProperties()) { - if (prop.Name == "Trace") continue; + if (prop.Name == "Trace") + { + continue; + } - Console.Out.WriteLine("{0} = {1}", prop.Name, prop.GetValue(runResult.ScenarioContext, null)); + Console.WriteLine("{0} = {1}", prop.Name, prop.GetValue(runResult.ScenarioContext, null)); } - Console.WriteLine(""); - Console.Out.WriteLine("Trace:"); - Console.Out.WriteLine(runResult.ScenarioContext.Trace); - - Console.Out.WriteLine("------------------------------------------------------"); + Console.WriteLine(); + Console.WriteLine("Trace:"); + Console.WriteLine(runResult.ScenarioContext.Trace); + Console.WriteLine("------------------------------------------------------"); } - static RunResult PerformTestRun(IList behaviorDescriptors, IList shoulds, RunDescriptor runDescriptor, Func done, Func allowedExceptions) + static async Task PerformTestRun(List behaviorDescriptors, List shoulds, RunDescriptor runDescriptor, Func done) { var runResult = new RunResult { @@ -151,47 +130,22 @@ static RunResult PerformTestRun(IList behaviorDescriptors, ILi }; var runTimer = new Stopwatch(); - runTimer.Start(); try { - var runners = InitializeRunners(runDescriptor, behaviorDescriptors); + var endpoints = await InitializeRunners(runDescriptor, behaviorDescriptors).ConfigureAwait(false); - try - { - runResult.ActiveEndpoints = runners.Select(r => r.EndpointName).ToList(); + runResult.ActiveEndpoints = endpoints.Select(r => r.EndpointName).ToList(); - PerformScenarios(runDescriptor, runners, () => - { - if (!string.IsNullOrEmpty(runDescriptor.ScenarioContext.Exceptions)) - { - var ex = new Exception(runDescriptor.ScenarioContext.Exceptions); - if (!allowedExceptions(ex)) - { - throw new Exception("Failures in endpoints"); - } - } - return done(runDescriptor.ScenarioContext); - }); - } - finally - { - if (runDescriptor.UseSeparateAppdomains) - { - UnloadAppDomains(runners); - } - } + await PerformScenarios(runDescriptor, endpoints, () => done(runDescriptor.ScenarioContext)).ConfigureAwait(false); runTimer.Stop(); - Parallel.ForEach(runners, runner => + foreach (var v in shoulds.Where(s => s.ContextType == runDescriptor.ScenarioContext.GetType())) { - foreach (var v in shoulds.Where(s => s.ContextType == runDescriptor.ScenarioContext.GetType())) - { - v.Verify(runDescriptor.ScenarioContext); - } - }); + v.Verify(runDescriptor.ScenarioContext); + } } catch (Exception ex) { @@ -204,21 +158,6 @@ static RunResult PerformTestRun(IList behaviorDescriptors, ILi return runResult; } - static void UnloadAppDomains(IEnumerable runners) - { - Parallel.ForEach(runners, runner => - { - try - { - AppDomain.Unload(runner.AppDomain); - } - catch (CannotUnloadAppDomainException ex) - { - Console.Out.WriteLine("Failed to unload appdomain {0}, reason: {1}", runner.AppDomain.FriendlyName, ex.ToString()); - } - }); - } - static IDictionary CreateRoutingTable(IEnumerable behaviorDescriptors) { var routingTable = new Dictionary(); @@ -231,40 +170,61 @@ static IDictionary CreateRoutingTable(IEnumerable> settings) + static void PrintSettings(IEnumerable> settings) { - Console.WriteLine(""); + Console.WriteLine(); Console.WriteLine("Using settings:"); foreach (var pair in settings) { - Console.Out.WriteLine(" {0}: {1}", pair.Key, pair.Value); + Console.WriteLine(" {0}: {1}", pair.Key, pair.Value); } Console.WriteLine(); } - static void PerformScenarios(RunDescriptor runDescriptor, IEnumerable runners, Func done) + static async Task PerformScenarios(RunDescriptor runDescriptor, IEnumerable runners, Func done) { + var cts = new CancellationTokenSource(); var endpoints = runners.Select(r => r.Instance).ToList(); - StartEndpoints(endpoints); - - runDescriptor.ScenarioContext.EndpointsStarted = true; - - var startTime = DateTime.UtcNow; - var maxTime = runDescriptor.TestExecutionTimeout; - - Task.WaitAll(endpoints.Select(endpoint => Task.Factory.StartNew(() => SpinWait.SpinUntil(done, maxTime))).Cast().ToArray(), maxTime); - + // ReSharper disable once LoopVariableIsNeverChangedInsideLoop try { - if ((DateTime.UtcNow - startTime) > maxTime) + await StartEndpoints(endpoints, cts).ConfigureAwait(false); + runDescriptor.ScenarioContext.EndpointsStarted = true; + await ExecuteWhens(endpoints, cts).ConfigureAwait(false); + + var startTime = DateTime.UtcNow; + var maxTime = runDescriptor.Settings.TestExecutionTimeout ?? TimeSpan.FromSeconds(90); + + while (!done() && !cts.Token.IsCancellationRequested) { - throw new ScenarioException(GenerateTestTimedOutMessage(maxTime)); + if (DateTime.UtcNow - startTime > maxTime) + { + ThrowOnFailedMessages(runDescriptor, endpoints); + throw new ScenarioException(GenerateTestTimedOutMessage(maxTime)); + } + + await Task.Delay(1).ConfigureAwait(false); } } finally { - StopEndpoints(endpoints); + await StopEndpoints(endpoints, runDescriptor.ScenarioContext).ConfigureAwait(false); + } + + ThrowOnFailedMessages(runDescriptor, endpoints); + } + + static void ThrowOnFailedMessages(RunDescriptor runDescriptor, List endpoints) + { + var unexpectedFailedMessages = runDescriptor.ScenarioContext.FailedMessages + .Where(kvp => endpoints.Single(e => e.Name() == kvp.Key).FailOnErrorMessage) + .SelectMany(kvp => kvp.Value) + .ToList(); + + if (unexpectedFailedMessages.Any()) + { + throw new MessagesFailedException(unexpectedFailedMessages, runDescriptor.ScenarioContext); } } @@ -272,115 +232,165 @@ static string GenerateTestTimedOutMessage(TimeSpan maxTime) { var sb = new StringBuilder(); - sb.AppendLine(string.Format("The maximum time limit for this test({0}s) has been reached", - maxTime.TotalSeconds)); + sb.AppendLine($"The maximum time limit for this test({maxTime.TotalSeconds}s) has been reached"); sb.AppendLine("----------------------------------------------------------------------------"); return sb.ToString(); } - static void StartEndpoints(IEnumerable endpoints) + static async Task StartEndpoints(IEnumerable endpoints, CancellationTokenSource cts) { - var tasks = endpoints.Select(endpoint => Task.Factory.StartNew(() => - { - var result = endpoint.Start(); + var tasks = endpoints.Select(endpoint => StartEndpoint(endpoint, cts)); + var whenAll = Task.WhenAll(tasks); + var timeoutTask = Task.Delay(TimeSpan.FromMinutes(2)); + var completedTask = await Task.WhenAny(whenAll, timeoutTask).ConfigureAwait(false); - if (result.Failed) - throw new ScenarioException("Endpoint failed to start", result.Exception); - })).ToArray(); - - if (!Task.WaitAll(tasks, TimeSpan.FromMinutes(2))) + if (completedTask.Equals(timeoutTask)) + { throw new Exception("Starting endpoints took longer than 2 minutes"); + } + await completedTask.ConfigureAwait(false); } - static void StopEndpoints(IEnumerable endpoints) + static async Task StartEndpoint(EndpointRunner endpoint, CancellationTokenSource cts) { - var tasks = endpoints.Select(endpoint => Task.Factory.StartNew(() => + var token = cts.Token; + try { - Console.Out.WriteLine("Stopping endpoint: {0}", endpoint.Name()); - var sw = new Stopwatch(); - sw.Start(); - var result = endpoint.Stop(); + await endpoint.Start(token).ConfigureAwait(false); + } + catch (Exception ex) + { + cts.Cancel(); + throw new ScenarioException("Endpoint failed to start", ex); + } + } - sw.Stop(); - if (result.Failed) - throw new ScenarioException("Endpoint failed to stop", result.Exception); + static async Task ExecuteWhens(IEnumerable endpoints, CancellationTokenSource cts) + { + var tasks = endpoints.Select(endpoint => ExecuteWhens(endpoint, cts)); + var whenAll = Task.WhenAll(tasks); + var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30)); + var completedTask = await Task.WhenAny(whenAll, timeoutTask).ConfigureAwait(false); - Console.Out.WriteLine("Endpoint: {0} stopped ({1}s)", endpoint.Name(), sw.Elapsed); - })).ToArray(); + if (completedTask.Equals(timeoutTask)) + { + throw new Exception("Executing given and whens took longer than 30 seconds."); + } - if (!Task.WaitAll(tasks, TimeSpan.FromMinutes(2))) - throw new Exception("Stopping endpoints took longer than 2 minutes"); + if (completedTask.IsFaulted && completedTask.Exception != null) + { + ExceptionDispatchInfo.Capture(completedTask.Exception).Throw(); + } } - static List InitializeRunners(RunDescriptor runDescriptor, IList behaviorDescriptors) + static async Task ExecuteWhens(EndpointRunner endpoint, CancellationTokenSource cts) { - var runners = new List(); - var routingTable = CreateRoutingTable(behaviorDescriptors); - - foreach (var behaviorDescriptor in behaviorDescriptors) + var token = cts.Token; + try { - var endpointName = GetEndpointNameForRun(behaviorDescriptor); + await endpoint.Whens(token).ConfigureAwait(false); + } + catch (Exception ex) + { + cts.Cancel(); + throw new ScenarioException("Whens failed to execute", ex); + } + } - if (endpointName.Length > 77) + static async Task StopEndpoints(IEnumerable endpoints, ScenarioContext scenarioContext) + { + var failBecauseUnhandledFailedMessage = false; + var startTime = DateTime.UtcNow; + var maxTime = TimeSpan.FromSeconds(30); + while (scenarioContext.UnfinishedFailedMessages.Values.Any(x => x)) + { + if (DateTime.UtcNow - startTime > maxTime) { - throw new Exception(string.Format("Endpoint name '{0}' is larger than 77 characters and will cause issues with MSMQ queue names. Please rename your test class or endpoint!",endpointName)); + failBecauseUnhandledFailedMessage = true; + break; } - var runner = PrepareRunner(endpointName, behaviorDescriptor.AppConfig, runDescriptor.UseSeparateAppdomains); - var result = runner.Instance.Initialize(runDescriptor, behaviorDescriptor, routingTable, endpointName); + await Task.Delay(1).ConfigureAwait(false); + } - if (runDescriptor.UseSeparateAppdomains) + var tasks = endpoints.Select(async endpoint => + { + Console.WriteLine("Stopping endpoint: {0}", endpoint.Name()); + var stopwatch = Stopwatch.StartNew(); + try { - // Extend the lease to the timeout value specified. - var serverLease = (ILease)RemotingServices.GetLifetimeService(runner.Instance); - - // Add the execution time + additional time for the endpoints to be able to stop gracefully - var totalLifeTime = runDescriptor.TestExecutionTimeout.Add(TimeSpan.FromMinutes(2)); - serverLease.Renew(totalLifeTime); + await endpoint.Stop().ConfigureAwait(false); + stopwatch.Stop(); + Console.WriteLine("Endpoint: {0} stopped ({1}s)", endpoint.Name(), stopwatch.Elapsed); } - - if (result.Failed) + catch (Exception ex) { - throw new ScenarioException(string.Format("Endpoint {0} failed to initialize", runner.Instance.Name()), result.Exception); + throw new ScenarioException("Endpoint failed to stop", ex); } + }); - runners.Add(runner); + var whenAll = Task.WhenAll(tasks); + var timeoutTask = Task.Delay(TimeSpan.FromMinutes(2)); + var completedTask = await Task.WhenAny(whenAll, timeoutTask).ConfigureAwait(false); + + if (failBecauseUnhandledFailedMessage) + { + throw new Exception("Some failed messages were not handled by the recoverability feature."); } - return runners; + if (completedTask == timeoutTask) + { + throw new Exception("Stopping endpoints took longer than 2 minutes"); + } } - static string GetEndpointNameForRun(EndpointBehavior endpointBehavior) + static async Task InitializeRunners(RunDescriptor runDescriptor, List endpointBehaviors) { - return Conventions.EndpointNamingConvention(endpointBehavior.EndpointBuilderType); - } + var routingTable = CreateRoutingTable(endpointBehaviors); - static ActiveRunner PrepareRunner(string endpointName, string appConfigPath, bool useSeparateAppdomains) - { - if (!useSeparateAppdomains) + var runnerInitializations = endpointBehaviors.Select(async endpointBehavior => { - return new ActiveRunner + var endpointName = GetEndpointNameForRun(endpointBehavior); + + if (endpointName.Length > 77) + { + throw new Exception($"Endpoint name '{endpointName}' is larger than 77 characters and will cause issues with MSMQ queue names. Rename the test class or endpoint."); + } + + var runner = new ActiveRunner { Instance = new EndpointRunner(), EndpointName = endpointName }; - } - var domainSetup = new AppDomainSetup - { - ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, - LoaderOptimization = LoaderOptimization.SingleDomain, - ConfigurationFile = appConfigPath ?? AppDomain.CurrentDomain.SetupInformation.ConfigurationFile - }; - var appDomain = AppDomain.CreateDomain(endpointName, AppDomain.CurrentDomain.Evidence, domainSetup); + try + { + await runner.Instance.Initialize(runDescriptor, endpointBehavior, routingTable, endpointName).ConfigureAwait(false); + } + catch (Exception e) + { + throw new ScenarioException($"Endpoint {runner.Instance.Name()} failed to initialize", e); + } + + return runner; + }); - return new ActiveRunner + try { - Instance = (EndpointRunner)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(EndpointRunner).FullName), - AppDomain = appDomain, - EndpointName = endpointName - }; + var x = await Task.WhenAll(runnerInitializations).ConfigureAwait(false); + return x; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + static string GetEndpointNameForRun(EndpointBehavior endpointBehavior) + { + return Conventions.EndpointNamingConvention(endpointBehavior.EndpointBuilderType); } } @@ -399,7 +409,9 @@ public IEnumerable ActiveEndpoints get { if (activeEndpoints == null) + { activeEndpoints = new List(); + } return activeEndpoints; } diff --git a/src/NServiceBus.AcceptanceTesting/Support/SimulatedException.cs b/src/NServiceBus.AcceptanceTesting/Support/SimulatedException.cs new file mode 100644 index 00000000000..7bc3a752583 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/SimulatedException.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.AcceptanceTesting +{ + using System; + using System.Runtime.Serialization; + + /// + /// A dummy exception to be used in acceptance tests for easier differentiation from real exceptions. + /// + public class SimulatedException : Exception + { + public SimulatedException() + { + } + + public SimulatedException(string message) : base(message) + { + } + + public SimulatedException(string message, Exception innerException) : base(message, innerException) + { + } + + protected SimulatedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/Support/WhenDefinition.cs b/src/NServiceBus.AcceptanceTesting/Support/WhenDefinition.cs new file mode 100644 index 00000000000..cdcbb7461a7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTesting/Support/WhenDefinition.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.AcceptanceTesting.Support +{ + using System; + using System.Threading.Tasks; + + public class WhenDefinition : IWhenDefinition where TContext : ScenarioContext + { + public WhenDefinition(Predicate condition, Func action) + { + Id = Guid.NewGuid(); + this.condition = condition; + messageAction = action; + } + + public WhenDefinition(Predicate condition, Func actionWithContext) + { + Id = Guid.NewGuid(); + this.condition = condition; + messageAndContextAction = actionWithContext; + } + + public Guid Id { get; } + + public async Task ExecuteAction(ScenarioContext context, IMessageSession session) + { + var c = (TContext)context; + + if (!condition(c)) + { + return false; + } + + if (messageAction != null) + { + await messageAction(session).ConfigureAwait(false); + } + else + { + await messageAndContextAction(session, c).ConfigureAwait(false); + } + + return true; + } + + Predicate condition; + Func messageAction; + Func messageAndContextAction; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTesting/packages.config b/src/NServiceBus.AcceptanceTesting/packages.config index 9d26bd13dba..bfa70365ccf 100644 --- a/src/NServiceBus.AcceptanceTesting/packages.config +++ b/src/NServiceBus.AcceptanceTesting/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_sendoptions.cs b/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_sendoptions.cs new file mode 100644 index 00000000000..0ce0fc1c4a6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_sendoptions.cs @@ -0,0 +1,89 @@ +namespace NServiceBus.AcceptanceTests.ApiExtension +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_extending_sendoptions : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_be_able_to_set_context_items_and_retrieve_it_via_a_behavior() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.GetExtensions().Set(new SendOptionsExtensions.TestingSendOptionsExtensionBehavior.Context + { + SomeValue = "I did it" + }); + options.RouteToThisEndpoint(); + + return session.Send(new SendMessage(), options); + })) + .Done(c => c.WasCalled) + .Run(); + + Assert.AreEqual("I did it", context.Secret); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public string Secret { get; set; } + } + + public class SendOptionsExtensions : EndpointConfigurationBuilder + { + public SendOptionsExtensions() + { + EndpointSetup(c => c.Pipeline.Register("TestingSendOptionsExtension", new TestingSendOptionsExtensionBehavior(), "Testing send options extensions")); + } + + class SendMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(SendMessage message, IMessageHandlerContext context) + { + TestContext.Secret = message.Secret; + TestContext.WasCalled = true; + return Task.FromResult(0); + } + } + + public class TestingSendOptionsExtensionBehavior : IBehavior + { + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + Context data; + if (context.Extensions.TryGet(out data)) + { + context.UpdateMessage(new SendMessage + { + Secret = data.SomeValue + }); + } + + return next(context); + } + + public class Context + { + public string SomeValue { get; set; } + } + } + } + + [Serializable] + public class SendMessage : ICommand + { + public string Secret { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_the_publish_api.cs b/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_the_publish_api.cs new file mode 100644 index 00000000000..575af173db7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ApiExtension/When_extending_the_publish_api.cs @@ -0,0 +1,116 @@ +namespace NServiceBus.AcceptanceTests.ApiExtension +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Routing; + using ScenarioDescriptors; + + public class When_extending_the_publish_api : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_make_the_context_available_to_behaviors() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber1Subscribed, session => + { + var options = new PublishOptions(); + + options.GetExtensions().Set(new Publisher.PublishExtensionBehavior.Context + { + SomeProperty = "ItWorks" + }); + + return session.Publish(new MyEvent(), options); + }) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.Subscriber1Subscribed = true; + } + })) + .Done(c => c.Subscriber1GotTheEvent) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.Subscriber1GotTheEvent)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool Subscriber1GotTheEvent { get; set; } + public bool Subscriber1Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.OnEndpointSubscribed((s, context) => { context.Subscriber1Subscribed = true; }); + + b.Pipeline.Register("PublishExtensionBehavior", new PublishExtensionBehavior(), "Testing publish extensions"); + }); + } + + public class PublishExtensionBehavior : IBehavior + { + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + Context data; + + if (context.Extensions.TryGet(out data)) + { + Assert.AreEqual("ItWorks", data.SomeProperty); + } + else + { + Assert.Fail("Expected to find the data"); + } + + return next(context); + } + + public class Context + { + public string SomeProperty { get; set; } + } + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(builder => { builder.DisableFeature(); }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Subscriber1GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_ForwardReceivedMessagesTo_is_set.cs b/src/NServiceBus.AcceptanceTests/Audit/When_ForwardReceivedMessagesTo_is_set.cs deleted file mode 100644 index f7ad7e855c8..00000000000 --- a/src/NServiceBus.AcceptanceTests/Audit/When_ForwardReceivedMessagesTo_is_set.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Audit -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - - public class When_ForwardReceivedMessagesTo_is_set : NServiceBusAcceptanceTest - { - [Test] - public void Should_forward_message() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => - { - bus.SendLocal(new MessageToForward()); - })) - .WithEndpoint() - .Done(c => c.GotForwardedMessage) - .Run(); - - Assert.IsTrue(context.GotForwardedMessage); - } - - public class Context : ScenarioContext - { - public bool GotForwardedMessage { get; set; } - } - - public class ForwardReceiver : EndpointConfigurationBuilder - { - public ForwardReceiver() - { - EndpointSetup(c => c.EndpointName("forward_receiver")); - } - - public class MessageToForwardHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToForward message) - { - Context.GotForwardedMessage = true; - } - } - } - - public class EndpointThatForwards : EndpointConfigurationBuilder - { - public EndpointThatForwards() - { - EndpointSetup() - .WithConfig(c => c.ForwardReceivedMessagesTo = "forward_receiver"); - } - - public class MessageToForwardHandler : IHandleMessages - { - public void Handle(MessageToForward message) - { - } - } - } - - [Serializable] - public class MessageToForward : IMessage - { - } - } - } diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_a_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Audit/When_a_message_is_audited.cs index 97b8e6c69d5..1d61d11cfbf 100644 --- a/src/NServiceBus.AcceptanceTests/Audit/When_a_message_is_audited.cs +++ b/src/NServiceBus.AcceptanceTests/Audit/When_a_message_is_audited.cs @@ -2,46 +2,41 @@ { using System; using System.Linq; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using MessageMutator; using NUnit.Framework; public class When_a_message_is_audited : NServiceBusAcceptanceTest { [Test] - public void Should_preserve_the_original_body() + public async Task Should_preserve_the_original_body() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.AuditChecksum != default(byte)) - .Run(); + var context = await Scenario.Define(c => { c.RunId = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToBeAudited + { + RunId = c.RunId + }))) + .WithEndpoint() + .Done(c => c.Done) + .Run(); Assert.AreEqual(context.OriginalBodyChecksum, context.AuditChecksum, "The body of the message sent to audit should be the same as the original message coming off the queue"); } - [Test] - public void Should_add_the_license_diagnostic_headers() + public static byte Checksum(byte[] data) { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.HasDiagnosticLicensingHeaders) - .Run(); - Assert.IsTrue(context.HasDiagnosticLicensingHeaders); + var longSum = data.Sum(x => (long) x); + return unchecked((byte) longSum); } - public class Context : ScenarioContext { + public Guid RunId { get; set; } + public bool Done { get; set; } public byte OriginalBodyChecksum { get; set; } public byte AuditChecksum { get; set; } - public bool HasDiagnosticLicensingHeaders { get; set; } } public class EndpointWithAuditOn : EndpointConfigurationBuilder @@ -56,10 +51,9 @@ class BodyMutator : IMutateIncomingTransportMessages, INeedInitialization { public Context Context { get; set; } - public void MutateIncoming(TransportMessage transportMessage) + public Task MutateIncoming(MutateIncomingTransportMessageContext context) { - - var originalBody = transportMessage.Body; + var originalBody = context.Body; Context.OriginalBodyChecksum = Checksum(originalBody); @@ -70,10 +64,11 @@ public void MutateIncoming(TransportMessage transportMessage) modifiedBody[modifiedBody.Length - 1] = 13; - transportMessage.Body = modifiedBody; + context.Body = modifiedBody; + return Task.FromResult(0); } - public void Customize(BusConfiguration configuration) + public void Customize(EndpointConfiguration configuration) { configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); } @@ -81,8 +76,9 @@ public void Customize(BusConfiguration configuration) public class MessageToBeAuditedHandler : IHandleMessages { - public void Handle(MessageToBeAudited message) + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) { + return Task.FromResult(0); } } } @@ -98,14 +94,13 @@ class BodySpy : IMutateIncomingTransportMessages, INeedInitialization { public Context Context { get; set; } - public void MutateIncoming(TransportMessage transportMessage) + public Task MutateIncoming(MutateIncomingTransportMessageContext transportMessage) { Context.AuditChecksum = Checksum(transportMessage.Body); - string licenseExpired; - Context.HasDiagnosticLicensingHeaders = transportMessage.Headers.TryGetValue(Headers.HasLicenseExpired, out licenseExpired); + return Task.FromResult(0); } - public void Customize(BusConfiguration configuration) + public void Customize(EndpointConfiguration configuration) { configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); } @@ -113,21 +108,26 @@ public void Customize(BusConfiguration configuration) public class MessageToBeAuditedHandler : IHandleMessages { - public void Handle(MessageToBeAudited message) + public Context TestContext { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) { + if (message.RunId != TestContext.RunId) + { + return Task.FromResult(0); + } + + TestContext.Done = true; + + return Task.FromResult(0); } } } - public static byte Checksum(byte[] data) - { - var longSum = data.Sum(x => (long)x); - return unchecked((byte)longSum); - } - [Serializable] public class MessageToBeAudited : IMessage { + public Guid RunId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_a_replymessage_is_audited.cs b/src/NServiceBus.AcceptanceTests/Audit/When_a_replymessage_is_audited.cs index fef223fa8ff..7fcb5108f07 100644 --- a/src/NServiceBus.AcceptanceTests/Audit/When_a_replymessage_is_audited.cs +++ b/src/NServiceBus.AcceptanceTests/Audit/When_a_replymessage_is_audited.cs @@ -2,33 +2,39 @@ { using System; using System.Linq; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using MessageMutator; - using NServiceBus.Features; using NUnit.Framework; public class When_a_replymessage_is_audited : NServiceBusAcceptanceTest { [Test] - public void Should_audit_the_message() + public async Task Should_audit_the_message() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint() - .WithEndpoint(b => b.Given(bus => bus.Send(new Request()))) - .WithEndpoint() - .Done(c => c.MessageAudited) - .Run(); - + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When(session => session.Send(new Request()))) + .WithEndpoint() + .Done(c => c.MessageAudited) + .Run(); + + Assert.True(context.MessageProcessed); Assert.True(context.MessageAudited); } + public static byte Checksum(byte[] data) + { + var longSum = data.Sum(x => (long) x); + return unchecked((byte) longSum); + } public class Context : ScenarioContext { public bool MessageAudited { get; set; } + public bool MessageProcessed { get; set; } } public class Server : EndpointConfigurationBuilder @@ -40,10 +46,13 @@ public Server() class RequestHandler : IHandleMessages { - public IBus Bus { get; set; } - public void Handle(Request message) + public Task Handle(Request message, IMessageHandlerContext context) { - Bus.Reply(new ResponseToBeAudited()); + var replyOptions = new ReplyOptions(); + + replyOptions.SetHeader("MyHeader", "SomeValue"); + + return context.Reply(new ResponseToBeAudited(), replyOptions); } } } @@ -52,17 +61,20 @@ public class EndpointWithAuditOn : EndpointConfigurationBuilder { public EndpointWithAuditOn() { - EndpointSetup(c=>c.DisableFeature()) + EndpointSetup(c => c.DisableFeature()) .AddMapping(typeof(Server)) .AuditTo(); } - public class MessageToBeAuditedHandler : IHandleMessages { - public void Handle(ResponseToBeAudited message) - { + public Context TestContext { get; set; } + public Task Handle(ResponseToBeAudited message, IMessageHandlerContext context) + { + Assert.AreEqual(context.MessageHeaders["MyHeader"], "SomeValue"); + TestContext.MessageProcessed = true; + return Task.FromResult(0); } } } @@ -78,12 +90,13 @@ class BodySpy : IMutateIncomingTransportMessages, INeedInitialization { public Context Context { get; set; } - public void MutateIncoming(TransportMessage transportMessage) + public Task MutateIncoming(MutateIncomingTransportMessageContext transportMessage) { Context.MessageAudited = true; + return Task.FromResult(0); } - public void Customize(BusConfiguration configuration) + public void Customize(EndpointConfiguration configuration) { configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); } @@ -91,27 +104,20 @@ public void Customize(BusConfiguration configuration) public class MessageToBeAuditedHandler : IHandleMessages { - public void Handle(ResponseToBeAudited message) + public Task Handle(ResponseToBeAudited message, IMessageHandlerContext context) { + return Task.FromResult(0); } } } - public static byte Checksum(byte[] data) - { - var longSum = data.Sum(x => (long)x); - return unchecked((byte)longSum); - } - [Serializable] public class ResponseToBeAudited : IMessage { } - class Request : IMessage { } } - -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_audit_is_overridden_in_code.cs b/src/NServiceBus.AcceptanceTests/Audit/When_audit_is_overridden_in_code.cs new file mode 100644 index 00000000000..a81673c59ee --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Audit/When_audit_is_overridden_in_code.cs @@ -0,0 +1,69 @@ +namespace NServiceBus.AcceptanceTests.Audit +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_audit_is_overridden_in_code : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_audit_to_target_queue() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.MessageAudited) + .Run(); + + Assert.True(context.MessageAudited); + } + + public class UserEndpoint : EndpointConfigurationBuilder + { + public UserEndpoint() + { + EndpointSetup(c => c.AuditProcessedMessagesTo("audit_with_code_target")); + } + + class Handler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class AuditSpy : EndpointConfigurationBuilder + { + public AuditSpy() + { + EndpointSetup() + .CustomEndpointName("audit_with_code_target"); + } + + class AuditMessageHandler : IHandleMessages + { + public Context MyContext { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + MyContext.MessageAudited = true; + return Task.FromResult(0); + } + } + } + + public class Context : ScenarioContext + { + public bool MessageAudited { get; set; } + } + + [Serializable] + public class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_auditing.cs b/src/NServiceBus.AcceptanceTests/Audit/When_auditing.cs index c26f3d63449..e8dcd527fb3 100644 --- a/src/NServiceBus.AcceptanceTests/Audit/When_auditing.cs +++ b/src/NServiceBus.AcceptanceTests/Audit/When_auditing.cs @@ -1,35 +1,34 @@ - -namespace NServiceBus.AcceptanceTests.Audit +namespace NServiceBus.AcceptanceTests.Audit { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; public class When_auditing : NServiceBusAcceptanceTest { [Test] - public void Should_not_be_forwarded_to_auditQueue_when_auditing_is_disabled() + public async Task Should_not_be_forwarded_to_auditQueue_when_auditing_is_disabled() { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.IsMessageHandlingComplete) + .Run(); Assert.IsFalse(context.IsMessageHandledByTheAuditEndpoint); } [Test] - public void Should_be_forwarded_to_auditQueue_when_auditing_is_enabled() + public async Task Should_be_forwarded_to_auditQueue_when_auditing_is_enabled() { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete && context.IsMessageHandledByTheAuditEndpoint) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.IsMessageHandlingComplete && c.IsMessageHandledByTheAuditEndpoint) + .Run(); Assert.IsTrue(context.IsMessageHandledByTheAuditEndpoint); } @@ -42,31 +41,29 @@ public class Context : ScenarioContext public class EndpointWithAuditOff : EndpointConfigurationBuilder { - public EndpointWithAuditOff() { // Although the AuditTo seems strange here, this test tries to fake the scenario where // even though the user has specified audit config, because auditing is explicitly turned // off, no messages should be audited. - EndpointSetup(c => c.DisableFeature()) + EndpointSetup(c => c.DisableFeature()) .AuditTo(); - } class MessageToBeAuditedHandler : IHandleMessages { public Context MyContext { get; set; } - public void Handle(MessageToBeAudited message) + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) { MyContext.IsMessageHandlingComplete = true; + return Task.FromResult(0); } } } public class EndpointWithAuditOn : EndpointConfigurationBuilder { - public EndpointWithAuditOn() { EndpointSetup() @@ -77,16 +74,16 @@ class MessageToBeAuditedHandler : IHandleMessages { public Context MyContext { get; set; } - public void Handle(MessageToBeAudited message) + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) { MyContext.IsMessageHandlingComplete = true; + return Task.FromResult(0); } } } public class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder { - public EndpointThatHandlesAuditMessages() { EndpointSetup(); @@ -96,9 +93,10 @@ class AuditMessageHandler : IHandleMessages { public Context MyContext { get; set; } - public void Handle(MessageToBeAudited message) + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) { MyContext.IsMessageHandledByTheAuditEndpoint = true; + return Task.FromResult(0); } } } @@ -108,4 +106,4 @@ public class MessageToBeAudited : IMessage { } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_auditing_message_with_TimeToBeReceived.cs b/src/NServiceBus.AcceptanceTests/Audit/When_auditing_message_with_TimeToBeReceived.cs new file mode 100644 index 00000000000..405e7f5cab5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Audit/When_auditing_message_with_TimeToBeReceived.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.AcceptanceTests.Audit +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_auditing_message_with_TimeToBeReceived : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_honor_TimeToBeReceived_for_audit_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.IsMessageHandlingComplete && c.TTBRHasExpiredAndMessageIsStillInAuditQueue) + .Run(); + + Assert.IsTrue(context.IsMessageHandlingComplete); + } + + class Context : ScenarioContext + { + public bool IsMessageHandlingComplete { get; set; } + public DateTime? FirstTimeProcessedByAudit { get; set; } + public bool TTBRHasExpiredAndMessageIsStillInAuditQueue { get; set; } + } + + class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup() + .AuditTo(); + } + + class MessageToBeAuditedHandler : IHandleMessages + { + public MessageToBeAuditedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + testContext.IsMessageHandlingComplete = true; + return Task.FromResult(0); + } + + Context testContext; + } + } + + class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesAuditMessages() + { + EndpointSetup(); + } + + class AuditMessageHandler : IHandleMessages + { + public AuditMessageHandler(Context textContext) + { + this.textContext = textContext; + } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + var auditProcessingStarted = DateTime.Now; + if (textContext.FirstTimeProcessedByAudit == null) + { + textContext.FirstTimeProcessedByAudit = auditProcessingStarted; + } + + var ttbr = TimeSpan.Parse(context.MessageHeaders[Headers.TimeToBeReceived]); + var ttbrExpired = auditProcessingStarted > textContext.FirstTimeProcessedByAudit.Value + ttbr; + if (ttbrExpired) + { + textContext.TTBRHasExpiredAndMessageIsStillInAuditQueue = true; + var timeElapsedSinceFirstHandlingOfAuditMessage = auditProcessingStarted - textContext.FirstTimeProcessedByAudit.Value; + Console.WriteLine("Audit message not removed because of TTBR({0}) after {1}. Succeeded.", ttbr, timeElapsedSinceFirstHandlingOfAuditMessage); + } + else + { + return context.HandleCurrentMessageLater(); + } + + return Task.FromResult(0); + } + + Context textContext; + } + } + + [Serializable] + [TimeToBeReceived("00:00:03")] + class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Audit/When_using_audit_message_is_received.cs b/src/NServiceBus.AcceptanceTests/Audit/When_using_audit_message_is_received.cs deleted file mode 100644 index 6d54cf56d9a..00000000000 --- a/src/NServiceBus.AcceptanceTests/Audit/When_using_audit_message_is_received.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Audit -{ - using System; - using System.Collections.Generic; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - - public class When_using_audit_message_is_received : NServiceBusAcceptanceTest - { - - [Test] - public void Should_contain_correct_headers() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete && context.IsMessageHandledByTheAuditEndpoint) - .Run(); - Assert.IsTrue(context.Headers.ContainsKey(Headers.ProcessingStarted)); - Assert.IsTrue(context.Headers.ContainsKey(Headers.ProcessingEnded)); - Assert.IsTrue(context.IsMessageHandledByTheAuditEndpoint); - } - - public class Context : ScenarioContext - { - public bool IsMessageHandlingComplete { get; set; } - public bool IsMessageHandledByTheAuditEndpoint { get; set; } - public IDictionary Headers{ get; set; } - } - - public class EndpointWithAuditOn : EndpointConfigurationBuilder - { - - public EndpointWithAuditOn() - { - EndpointSetup() - .AuditTo(); - } - - class MessageToBeAuditedHandler : IHandleMessages - { - Context context; - - public MessageToBeAuditedHandler(Context context) - { - this.context = context; - } - - public void Handle(MessageToBeAudited message) - { - context.IsMessageHandlingComplete = true; - } - } - } - - public class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder - { - - public EndpointThatHandlesAuditMessages() - { - EndpointSetup(); - } - - class AuditMessageHandler : IHandleMessages - { - Context context; - IBus bus; - - public AuditMessageHandler(Context context, IBus bus) - { - this.context = context; - this.bus = bus; - } - - public void Handle(MessageToBeAudited message) - { - context.IsMessageHandledByTheAuditEndpoint = true; - context.Headers = bus.CurrentMessageContext.Headers; - } - } - } - - [Serializable] - public class MessageToBeAudited : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_Deferring_a_message.cs b/src/NServiceBus.AcceptanceTests/Basic/When_Deferring_a_message.cs deleted file mode 100644 index 5864f646ecc..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_Deferring_a_message.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_Deferring_a_message : NServiceBusAcceptanceTest - { - [Test] - public void Message_should_be_received() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Defer(TimeSpan.FromSeconds(3), new MyMessage()))) - .Done(c => c.WasCalled) - .Run(); - - Assert.IsTrue(context.WasCalled); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(); - } - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - } - - [Serializable] - public class MyMessage : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_expired.cs b/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_expired.cs deleted file mode 100644 index fff08e5848b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_expired.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_TimeToBeReceived_has_expired : NServiceBusAcceptanceTest - { - [Test] - public void Message_should_not_be_received() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Run(TimeSpan.FromSeconds(10)); - Assert.IsFalse(context.WasCalled); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - } - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(c => c.Transactions().Disable()); //transactional msmq with ttbr not supported - } - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - } - - [Serializable] - [TimeToBeReceived("00:00:00")] - public class MyMessage : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_not_expired.cs b/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_not_expired.cs deleted file mode 100644 index 4cda3ec3a62..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_TimeToBeReceived_has_not_expired.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_TimeToBeReceived_has_not_expired : NServiceBusAcceptanceTest - { - [Test] - public void Message_should_be_received() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Run(); - - Assert.IsTrue(context.WasCalled); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(c => c.Transactions().Disable()); //transactional msmq with ttbr not supported - } - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - } - - [Serializable] - [TimeToBeReceived("00:00:10")] - public class MyMessage : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_a_callback_for_local_message.cs b/src/NServiceBus.AcceptanceTests/Basic/When_a_callback_for_local_message.cs deleted file mode 100644 index 3144d4d7d22..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_a_callback_for_local_message.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_a_callback_for_local_message : NServiceBusAcceptanceTest - { - [Test] - public void Should_trigger_the_callback_when_the_response_comes_back() - { - Scenario.Define() - .WithEndpoint(b=>b.Given( - (bus,context)=>bus.SendLocal(new MyRequest()).Register(r => - { - Assert.True(context.HandlerGotTheRequest); - context.CallbackFired = true; - }))) - .Done(c => c.CallbackFired) - .Repeat(r =>r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.CallbackFired); - Assert.True(c.HandlerGotTheRequest); - }) - .Run(); - } - - public class Context : ScenarioContext - { - public bool HandlerGotTheRequest { get; set; } - - public bool CallbackFired { get; set; } - } - - public class EndpointWithLocalCallback : EndpointConfigurationBuilder - { - public EndpointWithLocalCallback() - { - EndpointSetup(); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - Assert.False(Context.CallbackFired); - Context.HandlerGotTheRequest = true; - - Bus.Return(1); - } - } - } - - [Serializable] - public class MyRequest : IMessage{} - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_aborting_the_behavior_chain.cs b/src/NServiceBus.AcceptanceTests/Basic/When_aborting_the_behavior_chain.cs index 86c639c9210..4ea49e70e0d 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_aborting_the_behavior_chain.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_aborting_the_behavior_chain.cs @@ -1,19 +1,18 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_aborting_the_behavior_chain : NServiceBusAcceptanceTest { [Test] - public void Subsequent_handlers_will_not_be_invoked() + public async Task Subsequent_handlers_will_not_be_invoked() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new SomeMessage()))) + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new SomeMessage()))) .Done(c => c.FirstHandlerInvoked) .Run(); @@ -28,44 +27,40 @@ public class Context : ScenarioContext } [Serializable] - public class SomeMessage : IMessage { } + public class SomeMessage : IMessage + { + } public class MyEndpoint : EndpointConfigurationBuilder { public MyEndpoint() { - EndpointSetup(); - } - - class EnsureOrdering : ISpecifyMessageHandlerOrdering - { - public void SpecifyOrder(Order order) - { - order.Specify(First.Then()); - } + EndpointSetup(c => c.ExecuteTheseHandlersFirst(typeof(FirstHandler), typeof(SecondHandler))); } class FirstHandler : IHandleMessages { public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(SomeMessage message) + + public Task Handle(SomeMessage message, IMessageHandlerContext context) { Context.FirstHandlerInvoked = true; - Bus.DoNotContinueDispatchingCurrentMessageToHandlers(); + context.DoNotContinueDispatchingCurrentMessageToHandlers(); + + return Task.FromResult(0); } } class SecondHandler : IHandleMessages { public Context Context { get; set; } - - public void Handle(SomeMessage message) + + public Task Handle(SomeMessage message, IMessageHandlerContext context) { Context.SecondHandlerInvoked = true; + + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_callback_from_a_send_only.cs b/src/NServiceBus.AcceptanceTests/Basic/When_callback_from_a_send_only.cs deleted file mode 100644 index a9b8c090c2f..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_callback_from_a_send_only.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_callback_from_a_send_only : NServiceBusAcceptanceTest - { - [Test] - public void Should_throw() - { - Scenario.Define() - .WithEndpoint(b => b.Given((bus, c) => - { - var exception = Assert.Throws(() => bus.Send(new MyMessage()).Register(result => { })); - Assert.AreEqual("Callbacks are invalid in a sendonly endpoint.", exception.Message); - - })) - .WithEndpoint() - .Run(); - } - - public class Context : ScenarioContext - { - } - - public class SendOnlyEndpoint : EndpointConfigurationBuilder - { - public SendOnlyEndpoint() - { - EndpointSetup() - .SendOnly() - .AddMapping(typeof(Receiver)); - } - - } - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup(); - } - } - - [Serializable] - public class MyMessage : ICommand - { - } - - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_deferring_to_non_local.cs b/src/NServiceBus.AcceptanceTests/Basic/When_deferring_to_non_local.cs new file mode 100644 index 00000000000..0ef08034b3c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_deferring_to_non_local.cs @@ -0,0 +1,68 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_deferring_to_non_local : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_be_received() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromMilliseconds(3)); + return session.Send(new MyMessage(), options); + })) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(); + + Assert.IsTrue(context.WasCalled); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => config.EnableFeature()) + .AddMapping(typeof(Receiver)); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_typed_feature.cs b/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_typed_feature.cs new file mode 100644 index 00000000000..ec0d79f3b90 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_typed_feature.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_depending_on_typed_feature : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_enable_when_typed_dependency_enabled() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.EnableFeature(); + c.EnableFeature(); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.That(context.TypedDependencyFeatureSetUp, Is.True); + } + + [Test] + public async Task Should_disable_when_typed_dependency_disabled() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.DisableFeature(); + c.EnableFeature(); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.That(context.TypedDependencyFeatureSetUp, Is.False); + } + + class Context : ScenarioContext + { + public bool TypedDependencyFeatureSetUp { get; set; } + public bool UntypedDependencyFeatureSetUp { get; set; } + } + + public class EndpointWithFeatures : EndpointConfigurationBuilder + { + public EndpointWithFeatures() + { + EndpointSetup(); + } + } + + public class TypedDependentFeature : Feature + { + public TypedDependentFeature() + { + DependsOn(); + } + + protected override void Setup(FeatureConfigurationContext context) + { + var testContext = (Context) context.Settings.Get(); + testContext.TypedDependencyFeatureSetUp = true; + } + } + + public class DependencyFeature : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_untyped_feature.cs b/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_untyped_feature.cs new file mode 100644 index 00000000000..f40e8b1415d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_depending_on_untyped_feature.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_depending_on_untyped_feature : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_enable_when_untyped_dependency_enabled() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.EnableFeature(); + c.EnableFeature(); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.That(context.UntypedDependencyFeatureSetUp, Is.True); + } + + [Test] + public async Task Should_disable_when_untyped_dependency_disabled() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.DisableFeature(); + c.EnableFeature(); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.That(context.UntypedDependencyFeatureSetUp, Is.False); + } + + class Context : ScenarioContext + { + public bool UntypedDependencyFeatureSetUp { get; set; } + } + + public class EndpointWithFeatures : EndpointConfigurationBuilder + { + public EndpointWithFeatures() + { + EndpointSetup(); + } + } + + public class UntypedDependentFeature : Feature + { + public UntypedDependentFeature() + { + var featureTypeFullName = typeof(DependencyFeature).FullName; + DependsOn(featureTypeFullName); + } + + protected override void Setup(FeatureConfigurationContext context) + { + var testContext = (Context) context.Settings.Get(); + testContext.UntypedDependencyFeatureSetUp = true; + } + } + + public class DependencyFeature : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_extending_behavior_context.cs b/src/NServiceBus.AcceptanceTests/Basic/When_extending_behavior_context.cs new file mode 100644 index 00000000000..03f557d9a84 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_extending_behavior_context.cs @@ -0,0 +1,85 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_extending_behavior_context : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_be_available_in_handler_context() + { + ExtensionValue = Guid.NewGuid().ToString(); + + var context = await Scenario.Define() + .WithEndpoint(e => e + .When((session, c) => session.SendLocal(new SomeMessage()))) + .Done(c => c.HandlerAExtensionValue != null && c.HandlerBExtensionValue != null) + .Run(); + + Assert.AreEqual(ExtensionValue, context.HandlerAExtensionValue); + Assert.AreEqual(ExtensionValue, context.HandlerBExtensionValue); + } + + static string ExtensionValue; + + class Context : ScenarioContext + { + public string HandlerAExtensionValue { get; set; } + public string HandlerBExtensionValue { get; set; } + } + + class ContextExtendingEndpoint : EndpointConfigurationBuilder + { + public ContextExtendingEndpoint() + { + EndpointSetup(c => c.Pipeline.Register( + "CustomContextExtensionBehavior", + new CustomContextExtensionBehavior(), + "Puts customized data on the message context")); + } + + class MessageHandlerA : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + string extensionValue; + context.Extensions.TryGet("CustomExtension", out extensionValue); + TestContext.HandlerAExtensionValue = extensionValue; + return Task.FromResult(0); + } + } + + class MessageHandlerB : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + string extensionValue; + context.Extensions.TryGet("CustomExtension", out extensionValue); + TestContext.HandlerBExtensionValue = extensionValue; + return Task.FromResult(0); + } + } + + class CustomContextExtensionBehavior : IBehavior + { + public Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + context.Extensions.Set("CustomExtension", ExtensionValue); + return next(context); + } + } + } + + class SomeMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_handling_current_message_later.cs b/src/NServiceBus.AcceptanceTests/Basic/When_handling_current_message_later.cs index 51c46427e82..f0caf17b221 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_handling_current_message_later.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_handling_current_message_later.cs @@ -1,22 +1,22 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NServiceBus.Features; - using NServiceBus.UnitOfWork; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using UnitOfWork; public class When_handling_current_message_later : NServiceBusAcceptanceTest { [Test] - public void Should_commit_unit_of_work_and_execute_subsequent_handlers() + public async Task Should_commit_unit_of_work_and_execute_subsequent_handlers() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new SomeMessage()))) + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new SomeMessage + { + Id = c.Id + }))) .Done(c => c.Done) .Run(); @@ -27,6 +27,7 @@ public void Should_commit_unit_of_work_and_execute_subsequent_handlers() public class Context : ScenarioContext { + public Guid Id { get; set; } public bool Done { get; set; } public int FirstHandlerInvocationCount { get; set; } public int SecondHandlerInvocationCount { get; set; } @@ -40,66 +41,70 @@ public MyEndpoint() EndpointSetup(b => { b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - b.DisableFeature(); - b.DisableFeature(); - }) - .WithConfig(c => - { - c.MaxRetries = 0; - }); - } - - class EnsureOrdering : ISpecifyMessageHandlerOrdering - { - public void SpecifyOrder(Order order) - { - order.Specify(First.Then()); - } + b.ExecuteTheseHandlersFirst(typeof(FirstHandler), typeof(SecondHandler)); + }); } class CheckUnitOfWorkOutcome : IManageUnitsOfWork { public Context Context { get; set; } - public void Begin() + public Task Begin() { + return Task.FromResult(0); } - public void End(Exception ex = null) + public Task End(Exception ex = null) { - Context.UoWCommited = (ex == null); + Context.UoWCommited = ex == null; + return Task.FromResult(0); } } class FirstHandler : IHandleMessages { - public Context Context { get; set; } - public IBus Bus { get; set; } + public Context TestContext { get; set; } - public void Handle(SomeMessage message) + public Task Handle(SomeMessage message, IMessageHandlerContext context) { - Context.FirstHandlerInvocationCount++; + if (message.Id != TestContext.Id) + { + return Task.FromResult(0); + } + TestContext.FirstHandlerInvocationCount++; - if (Context.FirstHandlerInvocationCount == 1) + if (TestContext.FirstHandlerInvocationCount == 1) { - Bus.HandleCurrentMessageLater(); + return context.HandleCurrentMessageLater(); } + + return Task.FromResult(0); } } class SecondHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(SomeMessage message) + + public Task Handle(SomeMessage message, IMessageHandlerContext context) { + if (message.Id != Context.Id) + { + return Task.FromResult(0); + } + Context.SecondHandlerInvocationCount++; Context.Done = true; + + return Task.FromResult(0); } } } - [Serializable] - public class SomeMessage : IMessage { } + [Serializable] + public class SomeMessage : IMessage + { + public Guid Id { get; set; } + } } - } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_handling_message_with_several_messagehandlers.cs b/src/NServiceBus.AcceptanceTests/Basic/When_handling_message_with_several_messagehandlers.cs new file mode 100644 index 00000000000..03f523f9efb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_handling_message_with_several_messagehandlers.cs @@ -0,0 +1,82 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_handling_message_with_several_messagehandlers : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_call_all_handlers() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage + { + Id = c.Id + }))) + .Done(c => c.FirstHandlerWasCalled) + .Run(); + + Assert.True(context.FirstHandlerWasCalled); + Assert.True(context.SecondHandlerWasCalled); + } + + public class Context : ScenarioContext + { + public bool FirstHandlerWasCalled { get; set; } + public bool SecondHandlerWasCalled { get; set; } + public Guid Id { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup() + .AddMapping(typeof(Endpoint)); + } + } + + [Serializable] + public class MyMessage : IMessage + { + public Guid Id { get; set; } + } + + public class FirstMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (Context.Id != message.Id) + { + return Task.FromResult(0); + } + + Context.FirstHandlerWasCalled = true; + + return Task.FromResult(0); + } + } + + public class SecondMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (Context.Id != message.Id) + { + return Task.FromResult(0); + } + + Context.SecondHandlerWasCalled = true; + + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_incoming_headers_should_be_shared.cs b/src/NServiceBus.AcceptanceTests/Basic/When_incoming_headers_should_be_shared.cs deleted file mode 100644 index 2746b0b150c..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_incoming_headers_should_be_shared.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_incoming_headers_should_be_shared : NServiceBusAcceptanceTest - { - [Test] - public void Should_expose_header_in_downstream_handlers() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new Message()))) - .Done(c => c.GotMessage) - .Run(); - - Assert.IsTrue(context.SecondHandlerCanReadHeaderSetByFirstHandler); - } - - public class Context : ScenarioContext - { - public bool SecondHandlerCanReadHeaderSetByFirstHandler { get; set; } - public bool GotMessage { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(); - } - - class EnsureOrdering : ISpecifyMessageHandlerOrdering - { - public void SpecifyOrder(Order order) - { - order.Specify(First.Then()); - } - } - - class FirstHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(Message message) - { - Bus.SetMessageHeader(message, "Key", "Value"); - } - } - - class SecondHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public Context Context { get; set; } - - public void Handle(Message message) - { - var header = Bus.GetMessageHeader(message, "Key"); - Context.SecondHandlerCanReadHeaderSetByFirstHandler = header == "Value"; - Context.GotMessage = true; - } - } - } - - [Serializable] - public class Message : ICommand - { - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_injecting_handler_props.cs b/src/NServiceBus.AcceptanceTests/Basic/When_injecting_handler_props.cs deleted file mode 100644 index 19d0347ac6d..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_injecting_handler_props.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_injecting_handler_props : NServiceBusAcceptanceTest - { - [Test] - public void Run() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(c=>c.When(b=>b.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Run(); - - Assert.AreEqual(10, context.Number); - Assert.AreEqual("Foo", context.Name); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - public string Name { get; set; } - public int Number { get; set; } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup(c => - { - c.InitializeHandlerProperty("Number", 10); - c.InitializeHandlerProperty("Name", "Foo"); - }); - - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public string Name { get; set; } - - public int Number { get; set; } - - public void Handle(MyMessage message) - { - Context.Number = Number; - Context.Name = Name; - Context.WasCalled = true; - } - } - } - - [Serializable] - public class MyMessage : ICommand - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_multiple_mappings_exists.cs b/src/NServiceBus.AcceptanceTests/Basic/When_multiple_mappings_exists.cs index cde0bc4b39f..9c18e9aa61a 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_multiple_mappings_exists.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_multiple_mappings_exists.cs @@ -1,24 +1,22 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_multiple_mappings_exists : NServiceBusAcceptanceTest { [Test] - public void First_registration_should_be_the_final_destination() + public async Task First_registration_should_be_the_final_destination() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Send(new MyCommand1()))) - .WithEndpoint() - .WithEndpoint() - .Done(c => c.WasCalled1 || c.WasCalled2) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MyCommand1()))) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.WasCalled1 || c.WasCalled2) + .Run(); Assert.IsTrue(context.WasCalled1); Assert.IsFalse(context.WasCalled2); @@ -51,12 +49,10 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public IBus Bus { get; set; } - - public void Handle(MyBaseCommand message) + public Task Handle(MyBaseCommand message, IMessageHandlerContext context) { Context.WasCalled1 = true; - Thread.Sleep(2000); // Just to be sure the other receiver is finished + return Task.Delay(2000); // Just to be sure the other receiver is finished } } } @@ -72,12 +68,10 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public IBus Bus { get; set; } - - public void Handle(MyBaseCommand message) + public Task Handle(MyBaseCommand message, IMessageHandlerContext context) { Context.WasCalled2 = true; - Thread.Sleep(2000); // Just to be sure the other receiver is finished + return Task.Delay(2000); // Just to be sure the other receiver is finished } } } @@ -97,4 +91,4 @@ public class MyCommand2 : MyBaseCommand { } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_no_content_type.cs b/src/NServiceBus.AcceptanceTests/Basic/When_no_content_type.cs new file mode 100644 index 00000000000..99852a04c62 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_no_content_type.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_no_content_type : NServiceBusAcceptanceTest + { + [Test] + public Task Should_handle_message() + { + return Scenario.Define() + .WithEndpoint(b => b.When( + (session, c) => session.SendLocal(new Message + { + Property = "value" + }))) + .Done(c => c.ReceivedMessage) + .Run(); + } + + public class Context : ScenarioContext + { + public bool ReceivedMessage { get; set; } + } + + public class EndpointViaType : EndpointConfigurationBuilder + { + public EndpointViaType() + { + EndpointSetup(); + } + + public class Handler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Message request, IMessageHandlerContext context) + { + Context.ReceivedMessage = request.Property == "value"; + return Task.FromResult(0); + } + } + + class ContentTypeMutator : IMutateIncomingTransportMessages, INeedInitialization + { + public Context Context { get; set; } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + context.Headers.Remove(Headers.ContentType); + return Task.FromResult(0); + } + + public void Customize(EndpointConfiguration configuration) + { + configuration.RegisterComponents(c => + c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + } + } + } + + [Serializable] + public class Message : IMessage + { + public string Property { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_receiving_unobtrusive_message_without_handler.cs b/src/NServiceBus.AcceptanceTests/Basic/When_receiving_unobtrusive_message_without_handler.cs new file mode 100644 index 00000000000..2115788fec4 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_receiving_unobtrusive_message_without_handler.cs @@ -0,0 +1,65 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Logging; + using NUnit.Framework; + + public class When_receiving_unobtrusive_message_without_handler : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_be_moved_to_error_cause_handler_not_found() + { + var context = await Scenario.Define() + .WithEndpoint(c => c.When(s => s.Send(new MyCommand()))) + .WithEndpoint(c => c.DoNotFailOnErrorMessages()) + .Done(c => c.FailedMessages.Any()) + .Run(); + + Assert.True(context.Logs.Any(l => l.Level == LogLevel.Error && l.Message.Contains($"No handlers could be found for message type: { typeof(MyCommand).FullName}")), "No handlers could be found was not logged."); + Assert.False(context.Logs.Any(l => l.Level == LogLevel.Warn && l.Message.Contains($"Message header '{ typeof(MyCommand).FullName }' was mapped to type '{ typeof(MyCommand).FullName }' but that type was not found in the message registry, ensure the same message registration conventions are used in all endpoints, especially if using unobtrusive mode.")), "Message type could not be mapped."); + Assert.False(context.Logs.Any(l => l.Level == LogLevel.Warn && l.Message.Contains($"Could not determine message type from message header '{ typeof(MyCommand).FullName}'")), "Message type could not be mapped."); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyCommand).FullName); + + c.UseSerialization(); + }).AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyCommand).FullName); + + c.UseSerialization(); + }) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class MyCommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_receiving_with_catch_all_handlers_registered.cs b/src/NServiceBus.AcceptanceTests/Basic/When_receiving_with_catch_all_handlers_registered.cs new file mode 100644 index 00000000000..d023fcf72d5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_receiving_with_catch_all_handlers_registered.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_receiving_with_catch_all_handlers_registered : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_call_catch_all_handlers() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage + { + Id = c.Id + }))) + .Done(c => c.ObjectHandlerWasCalled && c.DynamicHandlerWasCalled && c.IMessageHandlerWasCalled) + .Run(); + + Assert.True(context.ObjectHandlerWasCalled); + Assert.True(context.DynamicHandlerWasCalled); + Assert.True(context.IMessageHandlerWasCalled); + } + + public class Context : ScenarioContext + { + public bool ObjectHandlerWasCalled { get; set; } + public bool DynamicHandlerWasCalled { get; set; } + public bool IMessageHandlerWasCalled { get; set; } + public Guid Id { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + } + + [Serializable] + public class MyMessage : IMessage + { + public Guid Id { get; set; } + } + + public class CatchAllHandler_object : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(object message, IMessageHandlerContext context) + { + var myMessage = (MyMessage) message; + if (Context.Id != myMessage.Id) + { + return Task.FromResult(0); + } + + Context.ObjectHandlerWasCalled = true; + return Task.FromResult(0); + } + } + + public class CatchAllHandler_dynamic : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(dynamic message, IMessageHandlerContext context) + { + var myMessage = (MyMessage) message; + if (Context.Id != myMessage.Id) + { + return Task.FromResult(0); + } + + Context.DynamicHandlerWasCalled = true; + + return Task.FromResult(0); + } + } + + public class CatchAllHandler_IMessage : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IMessage message, IMessageHandlerContext context) + { + var myMessage = (MyMessage) message; + if (Context.Id != myMessage.Id) + { + return Task.FromResult(0); + } + + Context.IMessageHandlerWasCalled = true; + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_registering_custom_serializer.cs b/src/NServiceBus.AcceptanceTests/Basic/When_registering_custom_serializer.cs deleted file mode 100644 index 2312663a7bf..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_registering_custom_serializer.cs +++ /dev/null @@ -1,148 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Runtime.Serialization.Formatters.Binary; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NServiceBus.MessageInterfaces.MessageMapper.Reflection; - using NServiceBus.Serialization; - using NUnit.Framework; - - public class When_registering_custom_serializer : NServiceBusAcceptanceTest - { - [Test] - public void Should_register_via_type() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given( - (bus, c) => bus.SendLocal(new MyRequest()))) - .Done(c => c.HandlerGotTheRequest) - .Run(); - - Assert.IsTrue(context.SerializeCalled); - Assert.IsTrue(context.DeserializeCalled); - } - - [Test] - public void Should_register_via_definition() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given( - (bus, c) => bus.SendLocal(new MyRequest()))) - .Done(c => c.HandlerGotTheRequest) - .Run(); - - Assert.IsTrue(context.SerializeCalled); - Assert.IsTrue(context.DeserializeCalled); - } - - public class Context : ScenarioContext - { - public bool HandlerGotTheRequest { get; set; } - public bool SerializeCalled { get; set; } - public bool DeserializeCalled { get; set; } - } - - public class EndpointViaType : EndpointConfigurationBuilder - { - public EndpointViaType() - { - EndpointSetup(c => c.UseSerialization(typeof(MyCustomSerializer))); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyRequest request) - { - Context.HandlerGotTheRequest = true; - } - } - } - - public class EndpointViaDefinition : EndpointConfigurationBuilder - { - public EndpointViaDefinition() - { - EndpointSetup(c => c.UseSerialization(typeof(MySuperSerializer))); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyRequest request) - { - Context.HandlerGotTheRequest = true; - } - } - } - - [Serializable] - public class MyRequest : IMessage - { - } - - class MySuperSerializer : SerializationDefinition - { - protected override Type ProvidedByFeature() - { - return typeof(MySuperSerializerFeature); - } - } - - class MySuperSerializerFeature : Feature - { - public MySuperSerializerFeature() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } - - class MyCustomSerializer : IMessageSerializer - { - public Context Context { get; set; } - - public void Serialize(object message, Stream stream) - { - var serializer = new BinaryFormatter(); - serializer.Serialize(stream, message); - - Context.SerializeCalled = true; - } - - public object[] Deserialize(Stream stream, IList messageTypes = null) - { - var serializer = new BinaryFormatter(); - - Context.DeserializeCalled = true; - stream.Position = 0; - var msg = serializer.Deserialize(stream); - - return new[] - { - msg - }; - } - - public string ContentType - { - get { return "MyCustomSerializer"; } - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_registering_handlers_explicitly.cs b/src/NServiceBus.AcceptanceTests/Basic/When_registering_handlers_explicitly.cs new file mode 100644 index 00000000000..8e99ddf3aab --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_registering_handlers_explicitly.cs @@ -0,0 +1,70 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_registering_handlers_explicitly : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_enable_properties_to_be_set() + { + var simpleValue = "SomeValue"; + + var context = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => c.RegisterComponents(r => r.ConfigureComponent(builder => new Endpoint.MyMessageHandler(builder.Build()) + { + MySimpleDependency = simpleValue + }, DependencyLifecycle.InstancePerCall))); + b.When((bus, c) => bus.SendLocal(new MyMessage())); + }) + .Done(c => c.WasCalled) + .Run(); + + Assert.AreEqual(simpleValue, context.PropertyValue); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public string PropertyValue { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public MyMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public string MySimpleDependency { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + testContext.PropertyValue = MySimpleDependency; + testContext.WasCalled = true; + return Task.FromResult(0); + } + + Context testContext; + } + public class MyPropDependency + { + } + } + + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_ensure_proper_headers.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_ensure_proper_headers.cs deleted file mode 100644 index 7d4b5f41e32..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_sending_ensure_proper_headers.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - class When_sending_ensure_proper_headers : NServiceBusAcceptanceTest - { - [Test] - public void Should_have_proper_headers_for_the_originating_endpoint() - { - var context = new Context - { - Id = Guid.NewGuid() - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, ctx) => bus.Send(m => - { - m.Id = ctx.Id; - }))) - .WithEndpoint() - .Done(c => c.WasCalled) - .Run(); - Assert.True(context.WasCalled, "The message handler should be called"); - Assert.AreEqual("SenderForEnsureProperHeadersTest", context.ReceivedHeaders[Headers.OriginatingEndpoint], "Message should contain the Originating endpoint"); - Assert.IsNotNullOrEmpty(context.ReceivedHeaders[Headers.OriginatingHostId], "OriginatingHostId cannot be null or empty"); - Assert.IsNotNullOrEmpty(context.ReceivedHeaders[Headers.OriginatingMachine], "Endpoint machine name cannot be null or empty"); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - public IDictionary ReceivedHeaders { get; set; } - public Guid Id { get; set; } - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - CustomEndpointName("SenderForEnsureProperHeadersTest"); - EndpointSetup() - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup(); - } - } - - [Serializable] - public class MyMessage : ICommand - { - public Guid Id { get; set; } - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - if (Context.Id != message.Id) - return; - - Context.ReceivedHeaders = Bus.CurrentMessageContext.Headers; - Context.WasCalled = true; - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_from_a_send_only.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_from_a_send_only.cs index 9d4ae954240..f3142153691 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_sending_from_a_send_only.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_sending_from_a_send_only.cs @@ -1,43 +1,36 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; public class When_sending_from_a_send_only : NServiceBusAcceptanceTest { [Test] - public void Should_receive_the_message() + public async Task Should_receive_the_message() { - var context = new Context - { - Id = Guid.NewGuid() - }; - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Send(new MyMessage - { - Id = c.Id - }))) - .WithEndpoint() - .Done(c => c.WasCalled) - .Run(); + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.Send(new MyMessage + { + Id = c.Id + }))) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(); Assert.True(context.WasCalled, "The message handler should be called"); } [Test] - public void Should_not_need_audit_or_fault_forwarding_config_to_start() + public async Task Should_not_need_audit_or_fault_forwarding_config_to_start() { - var context = new Context - { - Id = Guid.NewGuid() - }; - Scenario.Define(context) - .WithEndpoint() - .Done(c => c.SendOnlyEndpointWasStarted) - .Run(); + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint() + .Done(c => c.SendOnlyEndpointWasStarted) + .Run(); Assert.True(context.SendOnlyEndpointWasStarted, "The endpoint should have started without any errors"); } @@ -54,22 +47,44 @@ public class SendOnlyEndpoint : EndpointConfigurationBuilder { public SendOnlyEndpoint() { - EndpointSetup() - .SendOnly(); + EndpointSetup(c => { c.EnableFeature(); }).SendOnly(); } - public class Bootstrapper : IWantToRunWhenConfigurationIsComplete + public class Bootstrapper : Feature { - public Context Context { get; set; } + public Bootstrapper() + { + EnableByDefault(); + } + + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new MyTask(b.Build())); + } - public void Run(Configure config) + public class MyTask : FeatureStartupTask { - Context.SendOnlyEndpointWasStarted = true; + public MyTask(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + protected override Task OnStart(IMessageSession session) + { + scenarioContext.SendOnlyEndpointWasStarted = true; + return Task.FromResult(0); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + + readonly Context scenarioContext; } } } - public class Sender : EndpointConfigurationBuilder { public Sender() @@ -98,15 +113,16 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public IBus Bus { get; set; } - - public void Handle(MyMessage message) + public Task Handle(MyMessage message, IMessageHandlerContext context) { if (Context.Id != message.Id) - return; + { + return Task.FromResult(0); + } Context.WasCalled = true; + return Task.FromResult(0); } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_interface_message_with_conventions.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_interface_message_with_conventions.cs new file mode 100644 index 00000000000..44714d0fe7f --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Basic/When_sending_interface_message_with_conventions.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_interface_message_with_conventions : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_the_message() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When(async (session, c) => + { + await session.Send(m => m.Id = c.Id); + })) + .WithEndpoint() + .Done(c => c.MessageInterfaceReceived) + .Run(); + + Assert.True(context.MessageInterfaceReceived); + } + + public class Context : ScenarioContext + { + public bool MessageInterfaceReceived { get; set; } + public Guid Id { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(b => b.Conventions().DefiningMessagesAs(type => type.Name.EndsWith("Message"))) + .AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(builder => + { + builder.Conventions() + .DefiningMessagesAs(type => type.Name.EndsWith("Message")); + }); + } + + public class MyMessageInterfaceHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IMyInterfaceMessage interfaceMessage, IMessageHandlerContext context) + { + if (Context.Id != interfaceMessage.Id) + { + return Task.FromResult(0); + } + + Context.MessageInterfaceReceived = true; + + return Task.FromResult(0); + } + } + } + + public interface IMyInterfaceMessage + { + Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_to_another_endpoint.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_to_another_endpoint.cs index f50ef2732ae..99d2f6aea6f 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_sending_to_another_endpoint.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_sending_to_another_endpoint.cs @@ -2,39 +2,37 @@ { using System; using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_sending_to_another_endpoint : NServiceBusAcceptanceTest { [Test] - public void Should_receive_the_message() + public async Task Should_receive_the_message() { - var context = new Context - { - Id = Guid.NewGuid() - }; + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => + { + var sendOptions = new SendOptions(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => + sendOptions.SetHeader("MyHeader", "MyHeaderValue"); + sendOptions.SetMessageId("MyMessageId"); + + return session.Send(new MyMessage { - bus.OutgoingHeaders["MyStaticHeader"] = "StaticHeaderValue"; - bus.Send(m=> - { - m.Id = c.Id; - bus.SetMessageHeader(m, "MyHeader", "MyHeaderValue"); - }); - })) - .WithEndpoint() - .Done(c => c.WasCalled) - .Run(); + Id = c.Id + }, sendOptions); + })) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(); Assert.True(context.WasCalled, "The message handler should be called"); Assert.AreEqual(1, context.TimesCalled, "The message handler should only be invoked once"); Assert.AreEqual("StaticHeaderValue", context.ReceivedHeaders["MyStaticHeader"], "Static headers should be attached to outgoing messages"); Assert.AreEqual("MyHeaderValue", context.MyHeader, "Static headers should be attached to outgoing messages"); - } public class Context : ScenarioContext @@ -43,7 +41,7 @@ public class Context : ScenarioContext public int TimesCalled { get; set; } - public IDictionary ReceivedHeaders { get; set; } + public IReadOnlyDictionary ReceivedHeaders { get; set; } public Guid Id { get; set; } @@ -54,8 +52,7 @@ public class Sender : EndpointConfigurationBuilder { public Sender() { - EndpointSetup() - .AddMapping(typeof(Receiver)); + EndpointSetup(c => { c.AddHeaderToAllOutgoingMessages("MyStaticHeader", "StaticHeaderValue"); }).AddMapping(typeof(Receiver)); } } @@ -68,22 +65,26 @@ public Receiver() public class MyMessageHandler : IHandleMessages { - public Context Context { get; set; } - - public IBus Bus { get; set; } + public Context TestContext { get; set; } - public void Handle(MyMessage message) + public Task Handle(MyMessage message, IMessageHandlerContext context) { - if (Context.Id != message.Id) - return; + if (TestContext.Id != message.Id) + { + return Task.FromResult(0); + } + + Assert.AreEqual(context.MessageId, "MyMessageId"); + + TestContext.TimesCalled++; - Context.TimesCalled++; + TestContext.MyHeader = context.MessageHeaders["MyHeader"]; - Context.MyHeader = Bus.GetMessageHeader(message, "MyHeader"); + TestContext.ReceivedHeaders = context.MessageHeaders; - Context.ReceivedHeaders = Bus.CurrentMessageContext.Headers; + TestContext.WasCalled = true; - Context.WasCalled = true; + return Task.FromResult(0); } } } @@ -93,4 +94,4 @@ public class MyMessage : IMessage public Guid Id { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_conventions.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_conventions.cs index 13bea0e8f2d..142e17244ec 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_conventions.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_conventions.cs @@ -1,27 +1,33 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_sending_with_conventions : NServiceBusAcceptanceTest { [Test] - public void Should_receive_the_message() + public async Task Should_receive_the_message() { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => bus.SendLocal(new MyMessage - { - Id = context.Id - }))) - .Done(c => c.WasCalled) - .Run(); + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When(async(session, c) => + { + await session.SendLocal(m => m.Id = c.Id); + await session.SendLocal(m => m.Id = c.Id); + })) + .Done(c => c.MessageClassReceived && c.MessageInterfaceReceived) + .Run(); + + Assert.True(context.MessageClassReceived); + Assert.True(context.MessageInterfaceReceived); } public class Context : ScenarioContext { - public bool WasCalled { get; set; } + public bool MessageClassReceived { get; set; } + public bool MessageInterfaceReceived { get; set; } public Guid Id { get; set; } } @@ -29,28 +35,52 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(b => b.Conventions().DefiningMessagesAs(type => type == typeof(MyMessage))); + EndpointSetup(b => b.Conventions().DefiningMessagesAs(type => type.Name.EndsWith("Message"))); } } - [Serializable] public class MyMessage { public Guid Id { get; set; } } + public interface IMyInterfaceMessage + { + Guid Id { get; set; } + } + public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - - public void Handle(MyMessage message) + public Task Handle(MyMessage message, IMessageHandlerContext context) { if (Context.Id != message.Id) - return; + { + return Task.FromResult(0); + } + + Context.MessageClassReceived = true; + + return Task.FromResult(0); + } + } + + public class MyMessageInterfaceHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IMyInterfaceMessage interfaceMessage, IMessageHandlerContext context) + { + if (Context.Id != interfaceMessage.Id) + { + return Task.FromResult(0); + } + + Context.MessageInterfaceReceived = true; - Context.WasCalled = true; + return Task.FromResult(0); } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_no_correlation_id.cs b/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_no_correlation_id.cs deleted file mode 100644 index 46401b61ec9..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_sending_with_no_correlation_id.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; - using NUnit.Framework; - - public class When_sending_with_no_correlation_id : NServiceBusAcceptanceTest - { - [Test] - public void Should_use_the_message_id_as_the_correlation_id() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyRequest()))) - .Done(c => c.GotRequest) - .Run(); - - Assert.AreEqual(context.MessageIdReceived, context.CorrelationIdReceived, "Correlation id should match MessageId"); - } - - public class Context : ScenarioContext - { - public string MessageIdReceived { get; set; } - public bool GotRequest { get; set; } - public string CorrelationIdReceived { get; set; } - } - - public class CorrelationEndpoint : EndpointConfigurationBuilder - { - public CorrelationEndpoint() - { - EndpointSetup(); - } - - class GetValueOfIncomingCorrelationId : IMutateIncomingTransportMessages, INeedInitialization - { - public Context Context { get; set; } - - public void MutateIncoming(TransportMessage transportMessage) - { - Context.CorrelationIdReceived = transportMessage.CorrelationId; - Context.MessageIdReceived = transportMessage.Id; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } - - public class MyResponseHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest response) - { - Context.GotRequest = true; - } - } - } - - - [Serializable] - public class MyRequest : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_a_custom_correlation_id.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_a_custom_correlation_id.cs deleted file mode 100644 index e470ad0d6e5..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_a_custom_correlation_id.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; - using NUnit.Framework; - - public class When_using_a_custom_correlation_id : NServiceBusAcceptanceTest - { - static string CorrelationId = "my_custom_correlation_id"; - - [Test] - public void Should_use_the_given_id_as_the_transport_level_correlation_id() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new SendMessageWithCorrelation()))) - .Done(c => c.GotRequest) - .Run(); - - Assert.AreEqual(CorrelationId, context.CorrelationIdReceived, "Correlation ids should match"); - } - - public class Context : ScenarioContext - { - public bool GotRequest { get; set; } - - public string CorrelationIdReceived { get; set; } - } - - public class CorrelationEndpoint : EndpointConfigurationBuilder - { - public CorrelationEndpoint() - { - EndpointSetup(); - } - - class GetValueOfIncomingCorrelationId : IMutateIncomingTransportMessages, INeedInitialization - { - public Context Context { get; set; } - - public void MutateIncoming(TransportMessage transportMessage) - { - Context.CorrelationIdReceived = transportMessage.CorrelationId; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } - - public class SendMessageWithCorrelationHandler : IHandleMessages - { - public IBus Bus { get; set; } - public Configure Configure { get; set; } - - public void Handle(SendMessageWithCorrelation message) - { - Bus.Send(Configure.LocalAddress, CorrelationId, new MyRequest()); - } - } - - public class MyResponseHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest response) - { - Context.GotRequest = true; - } - } - } - - [Serializable] - public class SendMessageWithCorrelation : IMessage - { - } - - [Serializable] - public class MyRequest : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_a_greedy_convention.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_a_greedy_convention.cs index 0c106964467..99cf09286f7 100644 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_a_greedy_convention.cs +++ b/src/NServiceBus.AcceptanceTests/Basic/When_using_a_greedy_convention.cs @@ -1,24 +1,25 @@ namespace NServiceBus.AcceptanceTests.Basic { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_using_a_greedy_convention : NServiceBusAcceptanceTest { [Test] - public void Should_receive_the_message() + public async Task Should_receive_the_message() { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => bus.SendLocal(new MyMessage { Id = context.Id }))) - .Done(c => c.WasCalled) - .Repeat(r => r - .For(Transports.Default) - ) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage + { + Id = c.Id + }))) + .Done(c => c.WasCalled) + .Run(); + + Assert.True(context.WasCalled, "The message handler should be called"); } public class Context : ScenarioContext @@ -28,9 +29,9 @@ public class Context : ScenarioContext public Guid Id { get; set; } } - public class EndPoint : EndpointConfigurationBuilder + public class Endpoint : EndpointConfigurationBuilder { - public EndPoint() + public Endpoint() { EndpointSetup(c => c.Conventions().DefiningMessagesAs(MessageConvention)); } @@ -43,7 +44,7 @@ static bool MessageConvention(Type t) } [Serializable] - public class MyMessage + public class MyMessage { public Guid Id { get; set; } } @@ -51,12 +52,16 @@ public class MyMessage public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage message) + + public Task Handle(MyMessage message, IMessageHandlerContext context) { if (Context.Id != message.Id) - return; + { + return Task.FromResult(0); + } Context.WasCalled = true; + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_callback_to_get_message.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_callback_to_get_message.cs deleted file mode 100644 index b973a07e777..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_callback_to_get_message.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_using_callback_to_get_message : NServiceBusAcceptanceTest - { - [Test] - public void Should_receive_message() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given( - (bus, c) => bus.SendLocal(new MyRequest()).Register(r => - { - c.Reply = (MyReply)r.Messages.Single(); - c.CallbackFired = true; - }))) - .Done(c => c.CallbackFired) - .Run(); - - Assert.IsNotNull(context.Reply); - } - - public class Context : ScenarioContext - { - public bool CallbackFired { get; set; } - public MyReply Reply { get; set; } - } - - public class EndpointWithLocalCallback : EndpointConfigurationBuilder - { - public EndpointWithLocalCallback() - { - EndpointSetup(); - } - - public class MyRequestHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - Bus.Reply(new MyReply()); - } - } - } - - [Serializable] - public class MyRequest : IMessage { } - - [Serializable] - public class MyReply : IMessage { } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_from_older_versions.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_from_older_versions.cs deleted file mode 100644 index ce893674dac..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_from_older_versions.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.MessageMutator; - using NUnit.Framework; - - public class When_using_callbacks_from_older_versions : NServiceBusAcceptanceTest - { - [Test] - public void Should_trigger_the_callback() - { - Scenario.Define() - .WithEndpoint(b=>b.Given( - (bus,context)=>bus.SendLocal(new MyRequest()).Register(r => - { - Assert.True(context.HandlerGotTheRequest); - context.CallbackFired = true; - }))) - .Done(c => c.CallbackFired) - .Repeat(r =>r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.CallbackFired); - Assert.True(c.HandlerGotTheRequest); - }) - .Run(); - } - - public class Context : ScenarioContext - { - public bool HandlerGotTheRequest { get; set; } - public bool CallbackFired { get; set; } - } - - public class EndpointWithLocalCallback : EndpointConfigurationBuilder - { - public EndpointWithLocalCallback() - { - EndpointSetup(); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - Assert.False(Context.CallbackFired); - Context.HandlerGotTheRequest = true; - - Bus.Return(1); - } - } - } - - class BodyMutator : IMutateIncomingTransportMessages, INeedInitialization - { - public void MutateIncoming(TransportMessage transportMessage) - { - //early versions of did not have a Reply MessageIntent when Bus.Return is called - transportMessage.MessageIntent = MessageIntentEnum.Send; - transportMessage.Headers[Headers.NServiceBusVersion] = "3.3.0"; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } - - [Serializable] - public class MyRequest : IMessage{} - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_in_a_scaleout.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_in_a_scaleout.cs deleted file mode 100644 index 291f571f820..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_in_a_scaleout.cs +++ /dev/null @@ -1,127 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Support; - using NUnit.Framework; - - public class When_using_callbacks_in_a_scaleout : NServiceBusAcceptanceTest - { - [Test] - public void Each_client_should_have_a_unique_input_queue() - { - //to avoid processing each others callbacks - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.CustomConfig(c => RuntimeEnvironment.MachineNameAction = () => "ClientA") - .Given((bus, context) => bus.Send(new MyRequest { Id = context.Id, Client = RuntimeEnvironment.MachineName }) - .Register(r => context.CallbackAFired = true))) - .WithEndpoint(b => b.CustomConfig(c => RuntimeEnvironment.MachineNameAction = () => "ClientB") - .Given((bus, context) => bus.Send(new MyRequest { Id = context.Id, Client = RuntimeEnvironment.MachineName }) - .Register(r => context.CallbackBFired = true))) - .WithEndpoint() - .Done(c => c.ClientAGotResponse && c.ClientBGotResponse) - .Repeat(r => r.For() - ) - .Should(c => - { - Assert.True(c.CallbackAFired, "Callback on ClientA should fire"); - Assert.True(c.CallbackBFired, "Callback on ClientB should fire"); - Assert.False(c.ResponseEndedUpAtTheWrongClient, "One of the responses ended up at the wrong client"); - }) - .Run(new RunSettings { UseSeparateAppDomains = true }); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - - public bool ClientAGotResponse { get; set; } - - public bool ClientBGotResponse { get; set; } - - public bool ResponseEndedUpAtTheWrongClient { get; set; } - - public bool CallbackAFired { get; set; } - - public bool CallbackBFired { get; set; } - } - - public class Client : EndpointConfigurationBuilder - { - public Client() - { - EndpointSetup(c => c.ScaleOut().UseUniqueBrokerQueuePerMachine()) - .AddMapping(typeof(Server)); - } - - public class MyResponseHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyResponse response) - { - if (Context.Id != response.Id) - return; - - if (RuntimeEnvironment.MachineName == "ClientA") - Context.ClientAGotResponse = true; - else - { - Context.ClientBGotResponse = true; - } - - if (RuntimeEnvironment.MachineName != response.Client) - Context.ResponseEndedUpAtTheWrongClient = true; - } - } - } - - public class Server : EndpointConfigurationBuilder - { - public Server() - { - EndpointSetup(); - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - if (Context.Id != request.Id) - return; - - - Bus.Reply(new MyResponse { Id = request.Id, Client = request.Client }); - } - } - } - - [Serializable] - public class MyRequest : IMessage - { - public Guid Id { get; set; } - - public string Client { get; set; } - } - - [Serializable] - public class MyResponse : IMessage - { - public Guid Id { get; set; } - - public string Client { get; set; } - } - - - - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_with_messageid_eq_cid_.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_with_messageid_eq_cid_.cs deleted file mode 100644 index 2a8cdcaaafc..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_callbacks_with_messageid_eq_cid_.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; - using NServiceBus.Unicast.Messages; - using NUnit.Framework; - - public class When_using_callbacks_with_messageid_eq_cid_ : NServiceBusAcceptanceTest - { - [Test] - public void Should_trigger_the_callback() - { - var context = Scenario.Define() - .WithEndpoint(b=>b.Given( - (bus,c)=>bus.SendLocal(new MyRequest()).Register(r => - { - c.CallbackFired = true; - }))) - .Done(c => c.CallbackFired) - .Run(); - - Assert.True(context.CallbackFired); - } - - public class Context : ScenarioContext - { - public bool CallbackFired { get; set; } - } - - public class EndpointWithLocalCallback : EndpointConfigurationBuilder - { - public EndpointWithLocalCallback() - { - EndpointSetup(); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - Assert.False(Context.CallbackFired); - - Bus.Return(1); - } - } - } - - class BodyMutator : IMutateOutgoingTransportMessages, INeedInitialization - { - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - //to simulate native interop cases where MessageId == CorrelationId - transportMessage.Headers[Headers.MessageId] = transportMessage.Headers[Headers.CorrelationId]; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - - - } - - [Serializable] - public class MyRequest : IMessage{} - } -} diff --git a/src/NServiceBus.AcceptanceTests/Basic/When_using_ineedinitialization.cs b/src/NServiceBus.AcceptanceTests/Basic/When_using_ineedinitialization.cs deleted file mode 100644 index cd28866b418..00000000000 --- a/src/NServiceBus.AcceptanceTests/Basic/When_using_ineedinitialization.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Basic -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_using_ineedinitialization : NServiceBusAcceptanceTest - { - [Test] - public void Should_be_able_to_set_endpoint_name() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint() - .WithEndpoint() - .Done(c => c.WasCalled) - .Run(); - - Assert.True(context.WasCalled, "The message handler should be called"); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup(); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup() - .AddMapping(typeof(Sender)); - } - - public class SetEndpointName : INeedInitialization - { - public void Customize(BusConfiguration config) - { - config.EndpointName("ineedinitialization_receiver"); - } - } - - public class SendMessageToSender: IWantToRunWhenBusStartsAndStops - { - public IBus Bus { get; set; } - - public void Start() - { - Bus.Send(new SendMessage()); - } - - public void Stop() - { - } - } - } - - [Serializable] - public class SendMessage : ICommand - { - } - - [Serializable] - public class MyMessage : ICommand - { - public Guid Id { get; set; } - } - - public class SendMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(SendMessage message) - { - Bus.Send("ineedinitialization_receiver", new MyMessage()); - } - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command.cs new file mode 100644 index 00000000000..5be9f0d6aeb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_publishing_command : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.Publish(new MyCommand()); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + Assert.IsInstanceOf(context.Exception); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + } + + public class MyCommand : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled.cs new file mode 100644 index 00000000000..3a8956efcb3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled.cs @@ -0,0 +1,58 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_publishing_command_bestpractices_disabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_allow_publishing_commands() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var publishOptions = new PublishOptions(); + publishOptions.DoNotEnforceBestPractices(); + + return session.Publish(new MyCommand(), publishOptions); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.True(context.EndpointsStarted); + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup() + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages, IHandleMessages + { + public Task Handle(MyCommand message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled_on_endpoint.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled_on_endpoint.cs new file mode 100644 index 00000000000..e32ec4385dc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_publishing_command_bestpractices_disabled_on_endpoint.cs @@ -0,0 +1,63 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_publishing_command_bestpractices_disabled_on_endpoint : NServiceBusAcceptanceTest + { + [Test] + public Task Should_allow_publishing_commands() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Publish(new MyCommand()))) + .Done(c => c.EndpointsStarted) + .Run(); + } + + [Test] + public Task Should_allow_sending_events() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MyEvent()))) + .Done(c => c.EndpointsStarted) + .Run(); + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.DoNotEnforceBestPractices(); + }) + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages, IHandleMessages + { + public Task Handle(MyCommand message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_event.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_event.cs new file mode 100644 index 00000000000..42c71afe4c4 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_event.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_event : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.SendLocal(new MyEvent()); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + Assert.IsInstanceOf(context.Exception); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled.cs new file mode 100644 index 00000000000..3b026d4ae92 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled.cs @@ -0,0 +1,54 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_events_bestpractices_disabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_allow_sending_events() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var sendOptions = new SendOptions(); + sendOptions.DoNotEnforceBestPractices(); + + return session.Send(new MyEvent(), sendOptions); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.True(context.EndpointsStarted); + } + + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup() + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled_on_endpoint.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled_on_endpoint.cs new file mode 100644 index 00000000000..d997755dd68 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_sending_events_bestpractices_disabled_on_endpoint.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_events_bestpractices_disabled_on_endpoint : NServiceBusAcceptanceTest + { + [Test] + public Task Should_allow_sending_events() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MyEvent()))) + .Done(c => c.EndpointsStarted) + .Run(); + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.DoNotEnforceBestPractices(); + }) + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command.cs new file mode 100644 index 00000000000..8f72054cec7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_subscribing_to_command : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.Subscribe(); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + Assert.IsInstanceOf(context.Exception); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + } + + public class MyCommand : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command_bestpractices_disabled_on_endpoint.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command_bestpractices_disabled_on_endpoint.cs new file mode 100644 index 00000000000..b53db2099bc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_subscribing_to_command_bestpractices_disabled_on_endpoint.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_subscribing_to_command_bestpractices_disabled_on_endpoint : NServiceBusAcceptanceTest + { + [Test] + public Task Should_allow_subscribing_to_commands() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Subscribe())) + .Done(c => c.EndpointsStarted) + .Run(); + } + + [Test] + public Task Should_allow_publishing_commands() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Publish(new MyCommand()))) + .Done(c => c.EndpointsStarted) + .Run(); + } + + [Test] + public Task Should_allow_sending_events() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MyEvent()))) + .Done(c => c.EndpointsStarted) + .Run(); + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.DoNotEnforceBestPractices(); + }) + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages, IHandleMessages + { + public Task Handle(MyCommand message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command.cs new file mode 100644 index 00000000000..1c40cf57561 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_unsubscribing_to_command : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.Unsubscribe(); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + Assert.IsInstanceOf(context.Exception); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + } + + public class MyCommand : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command_bestpractices_disabled_on_endpoint.cs b/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command_bestpractices_disabled_on_endpoint.cs new file mode 100644 index 00000000000..2434a70a758 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/BestPractices/When_unsubscribing_to_command_bestpractices_disabled_on_endpoint.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_unsubscribing_to_command_bestpractices_disabled_on_endpoint : NServiceBusAcceptanceTest + { + [Test] + public Task Should_allow_unsubscribing_to_commands() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Unsubscribe())) + .Done(c => c.EndpointsStarted) + .Run(); + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.DoNotEnforceBestPractices(); + }) + .AddMapping(typeof(Endpoint)) + .AddMapping(typeof(Endpoint)); + } + + public class Handler : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_audited.cs new file mode 100644 index 00000000000..be275e96af9 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_audited.cs @@ -0,0 +1,104 @@ +namespace NServiceBus.AcceptanceTests.Causation +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_a_message_is_audited : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_flow_causation_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new FirstMessage()))) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.AreEqual(context.OriginRelatedTo, context.RelatedTo, "The RelatedTo header in audit message should be be equal to RelatedTo header in origin."); + Assert.AreEqual(context.OriginConversationId, context.ConversationId, "The ConversationId header in audit message should be be equal to ConversationId header in origin."); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string RelatedTo { get; set; } + public string ConversationId { get; set; } + public string OriginRelatedTo { get; set; } + public string OriginConversationId { get; set; } + } + + public class CausationEndpoint : EndpointConfigurationBuilder + { + public CausationEndpoint() + { + EndpointSetup().AuditTo(); + } + + public Context Context { get; set; } + + public class MessageSentInsideHandlersHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + + public class FirstMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(FirstMessage message, IMessageHandlerContext context) + { + TestContext.OriginRelatedTo = context.MessageId; + TestContext.OriginConversationId = context.MessageHeaders.ContainsKey(Headers.ConversationId) ? context.MessageHeaders[Headers.ConversationId] : null; + + context.SendLocal(new MessageToBeAudited()); + return Task.FromResult(0); + } + } + } + + class AuditSpyEndpoint : EndpointConfigurationBuilder + { + public AuditSpyEndpoint() + { + EndpointSetup(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + TestContext.RelatedTo = context.MessageHeaders.ContainsKey(Headers.RelatedTo) ? context.MessageHeaders[Headers.RelatedTo] : null; + TestContext.ConversationId = context.MessageHeaders.ContainsKey(Headers.ConversationId) ? context.MessageHeaders[Headers.ConversationId] : null; + TestContext.Done = true; + + return Task.FromResult(0); + } + } + + public class FirstMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(FirstMessage message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class FirstMessage : IMessage + { + } + + public class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_faulted.cs b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_faulted.cs new file mode 100644 index 00000000000..6b2ace739f2 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_faulted.cs @@ -0,0 +1,105 @@ +namespace NServiceBus.AcceptanceTests.Causation +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_a_message_is_faulted : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_flow_causation_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new FirstMessage())).DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.AreEqual(context.OriginRelatedTo, context.RelatedTo, "The RelatedTo header in fault message should be be equal to RelatedTo header in origin."); + Assert.AreEqual(context.OriginConversationId, context.ConversationId, "The ConversationId header in fault message should be be equal to ConversationId header in origin."); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string RelatedTo { get; set; } + public string ConversationId { get; set; } + public string OriginRelatedTo { get; set; } + public string OriginConversationId { get; set; } + } + + public class CausationEndpoint : EndpointConfigurationBuilder + { + public CausationEndpoint() + { + EndpointSetup((c, r) => + { + c.SendFailedMessagesTo("errorQueueForAcceptanceTest"); + }); + } + + public Context Context { get; set; } + + public class FirstMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(FirstMessage message, IMessageHandlerContext context) + { + TestContext.OriginRelatedTo = context.MessageId; + TestContext.OriginConversationId = context.MessageHeaders.ContainsKey(Headers.ConversationId) ? context.MessageHeaders[Headers.ConversationId] : null; + + context.SendLocal(new MessageThatFails()); + return Task.FromResult(0); + } + } + + public class MessageSentInsideHandlersHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + class EndpointThatHandlesErrorMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesErrorMessages() + { + EndpointSetup() + .CustomEndpointName("errorQueueForAcceptanceTest"); + } + + class ErrorMessageHandler : IHandleMessages + { + public ErrorMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + testContext.RelatedTo = context.MessageHeaders.ContainsKey(Headers.RelatedTo) ? context.MessageHeaders[Headers.RelatedTo] : null; + testContext.ConversationId = context.MessageHeaders.ContainsKey(Headers.ConversationId) ? context.MessageHeaders[Headers.ConversationId] : null; + testContext.Done = true; + + return Task.FromResult(0); + } + + Context testContext; + } + } + + public class FirstMessage : IMessage + { + } + + public class MessageThatFails : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_sent.cs b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_sent.cs new file mode 100644 index 00000000000..a1631cf3a09 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Causation/When_a_message_is_sent.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.AcceptanceTests.Causation +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_a_message_is_sent : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_flow_causation_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageSentOutsideOfHandler()))) + .Done(c => c.Done) + .Run(); + + Assert.AreEqual(context.FirstConversationId, context.ConversationIdReceived, "Conversation id should flow to outgoing messages"); + Assert.AreEqual(context.MessageIdOfFirstMessage, context.RelatedToReceived, "RelatedToId on outgoing messages should be set to the message id of the message causing it to be sent"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string FirstConversationId { get; set; } + public string ConversationIdReceived { get; set; } + public string MessageIdOfFirstMessage { get; set; } + public string RelatedToReceived { get; set; } + } + + public class CausationEndpoint : EndpointConfigurationBuilder + { + public CausationEndpoint() + { + EndpointSetup(); + } + + public Context Context { get; set; } + + public class MessageSentOutsideHandlersHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageSentOutsideOfHandler message, IMessageHandlerContext context) + { + TestContext.FirstConversationId = context.MessageHeaders[Headers.ConversationId]; + TestContext.MessageIdOfFirstMessage = context.MessageId; + + return context.SendLocal(new MessageSentInsideHandler()); + } + } + + public class MessageSentInsideHandlersHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageSentInsideHandler message, IMessageHandlerContext context) + { + TestContext.ConversationIdReceived = context.MessageHeaders[Headers.ConversationId]; + + TestContext.RelatedToReceived = context.MessageHeaders[Headers.RelatedTo]; + + TestContext.Done = true; + + return Task.FromResult(0); + } + } + } + + public class MessageSentOutsideOfHandler : IMessage + { + } + + public class MessageSentInsideHandler : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When_IWantToRunWhenBusStartsAndStops_Start_throws.cs b/src/NServiceBus.AcceptanceTests/Config/When_IWantToRunWhenBusStartsAndStops_Start_throws.cs deleted file mode 100644 index 897f2e8882a..00000000000 --- a/src/NServiceBus.AcceptanceTests/Config/When_IWantToRunWhenBusStartsAndStops_Start_throws.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Config -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_Start_throws : NServiceBusAcceptanceTest - { - [Test] - public void Should_shutdown_bus_cleanly() - { - Scenario.Define() - .WithEndpoint() - .Done(c => c.IsDone) - .Run(); - } - - public class Context : ScenarioContext - { - public bool IsDone { get; set; } - } - - public class StartedEndpoint : EndpointConfigurationBuilder - { - public StartedEndpoint() - { - EndpointSetup(); - } - - class AfterConfigIsComplete:IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - - public void Start() - { - Context.IsDone = true; - - throw new Exception("Boom!"); - } - - public void Stop() - { - } - } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When__startup_is_complete.cs b/src/NServiceBus.AcceptanceTests/Config/When__startup_is_complete.cs deleted file mode 100644 index de64e426a5e..00000000000 --- a/src/NServiceBus.AcceptanceTests/Config/When__startup_is_complete.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Config -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Settings; - using NUnit.Framework; - - public class When__startup_is_complete : NServiceBusAcceptanceTest - { - [Test] - public void Configure_and_setting_should_be_available_via_DI() - { - var context = Scenario.Define() - .WithEndpoint() - .Done(c => c.IsDone) - .Run(); - - Assert.True(context.ConfigureIsAvailable,"Configure should be available in DI"); - Assert.True(context.SettingIsAvailable, "Setting should be available in DI"); - } - - public class Context : ScenarioContext - { - public bool IsDone { get; set; } - public bool ConfigureIsAvailable { get; set; } - public bool SettingIsAvailable { get; set; } - } - - public class StartedEndpoint : EndpointConfigurationBuilder - { - public StartedEndpoint() - { - EndpointSetup(); - } - - class AfterConfigIsComplete:IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - - public Configure Configure { get; set; } - - public ReadOnlySettings Settings { get; set; } - - - public void Start() - { - Context.ConfigureIsAvailable = Configure != null; - - Context.SettingIsAvailable = Settings != null; - - Context.IsDone = true; - } - - public void Stop() - { - } - } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When_a_config_override_is_found.cs b/src/NServiceBus.AcceptanceTests/Config/When_a_config_override_is_found.cs deleted file mode 100644 index ea101a83726..00000000000 --- a/src/NServiceBus.AcceptanceTests/Config/When_a_config_override_is_found.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Config -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NServiceBus.Config.ConfigurationSource; - using NUnit.Framework; - - public class When_a_config_override_is_found : NServiceBusAcceptanceTest - { - static Address CustomErrorQ = Address.Parse("MyErrorQ"); - - [Test] - public void Should_be_used_instead_of_pulling_the_settings_from_appconfig() - { - var context = Scenario.Define() - .WithEndpoint().Done(c =>c.ErrorQueueUsedByTheEndpoint != null) - .Run(); - - Assert.AreEqual(CustomErrorQ, context.ErrorQueueUsedByTheEndpoint, "The error queue should have been changed"); - - } - - public class Context : ScenarioContext - { - public bool IsDone { get; set; } - - public Address ErrorQueueUsedByTheEndpoint { get; set; } - } - - public class ConfigOverrideEndpoint : EndpointConfigurationBuilder - { - - public ConfigOverrideEndpoint() - { - EndpointSetup(); - } - - public class ErrorQueueExtractor:IWantToRunWhenBusStartsAndStops - { - Configure configure; - Context context; - - public ErrorQueueExtractor(Configure configure, Context context) - { - this.configure = configure; - this.context = context; - } - - public void Start() - { - context.ErrorQueueUsedByTheEndpoint = Address.Parse(configure.Settings.GetConfigSection().ErrorQueue); - } - - public void Stop() - { - } - } - - public class ConfigErrorQueue : IProvideConfiguration - { - public MessageForwardingInCaseOfFaultConfig GetConfiguration() - { - - return new MessageForwardingInCaseOfFaultConfig - { - ErrorQueue = CustomErrorQ.ToString() - }; - } - } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When_injecting_message_session_into_handlers.cs b/src/NServiceBus.AcceptanceTests/Config/When_injecting_message_session_into_handlers.cs new file mode 100644 index 00000000000..8f5ac521279 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Config/When_injecting_message_session_into_handlers.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.AcceptanceTests.Config +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_injecting_message_session_into_handlers : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_on_startup() + { + var exception = Assert.ThrowsAsync(async () => await Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Run()); + + StringAssert.Contains("IMessageSession", exception.ToString()); + } + + public class StartedEndpoint : EndpointConfigurationBuilder + { + public StartedEndpoint() + { + EndpointSetup(); + } + + class MyHandler : IHandleMessages + { + //not supported + public IMessageSession MessageSession { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + return MessageSession.Send(new MyMessage()); + } + } + } + + class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When_only_abstract_config_override_is_found.cs b/src/NServiceBus.AcceptanceTests/Config/When_only_abstract_config_override_is_found.cs new file mode 100644 index 00000000000..6bd36812e87 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Config/When_only_abstract_config_override_is_found.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.AcceptanceTests.Config +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Config; + using NServiceBus.Config.ConfigurationSource; + using NUnit.Framework; + + public class When_only_abstract_config_override_is_found : NServiceBusAcceptanceTest + { + [Test] + public Task Should_not_invoke_it() + { + return Scenario.Define() + .WithEndpoint().Done(c => c.EndpointsStarted) + .Run(); + } + + public class ConfigOverrideEndpoint : EndpointConfigurationBuilder + { + public ConfigOverrideEndpoint() + { + EndpointSetup(); + } + + abstract class ConfigErrorQueue : IProvideConfiguration + { + public MessageForwardingInCaseOfFaultConfig GetConfiguration() + { + throw new NotImplementedException(); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Config/When_startup_is_complete.cs b/src/NServiceBus.AcceptanceTests/Config/When_startup_is_complete.cs new file mode 100644 index 00000000000..288f26708aa --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Config/When_startup_is_complete.cs @@ -0,0 +1,70 @@ +namespace NServiceBus.AcceptanceTests.Config +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using Settings; + + public class When_startup_is_complete : NServiceBusAcceptanceTest + { + [Test] + public async Task Settings_should_be_available_via_DI() + { + var context = await Scenario.Define() + .WithEndpoint() + .Done(c => c.IsDone) + .Run(); + + Assert.True(context.SettingIsAvailable, "Setting should be available in DI"); + } + + class Context : ScenarioContext + { + public bool IsDone { get; set; } + public bool SettingIsAvailable { get; set; } + } + + class AfterConfigIsCompleteFeature : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new AfterConfigIsCompleteFeatureTask(b.Build(), b.Build())); + } + + class AfterConfigIsCompleteFeatureTask : FeatureStartupTask + { + public AfterConfigIsCompleteFeatureTask(ReadOnlySettings settings, Context context) + { + this.settings = settings; + this.context = context; + } + + protected override Task OnStart(IMessageSession session) + { + context.SettingIsAvailable = settings != null; + + context.IsDone = true; + return Task.FromResult(0); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + + Context context; + ReadOnlySettings settings; + } + } + + public class StartedEndpoint : EndpointConfigurationBuilder + { + public StartedEndpoint() + { + EndpointSetup(c => c.EnableFeature()); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ConfigureEndpointInMemoryPersistence.cs b/src/NServiceBus.AcceptanceTests/ConfigureEndpointInMemoryPersistence.cs new file mode 100644 index 00000000000..ae6bd93d33d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ConfigureEndpointInMemoryPersistence.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting.Support; + +public class ConfigureEndpointInMemoryPersistence : IConfigureEndpointTestExecution +{ + public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings) + { + configuration.UsePersistence(); + return Task.FromResult(0); + } + + public Task Cleanup() + { + // Nothing required for in-memory persistence + return Task.FromResult(0); + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ConfigureEndpointMsmqTransport.cs b/src/NServiceBus.AcceptanceTests/ConfigureEndpointMsmqTransport.cs new file mode 100644 index 00000000000..352f567e5e7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ConfigureEndpointMsmqTransport.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Messaging; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.AcceptanceTests.ScenarioDescriptors; +using NServiceBus.Configuration.AdvanceExtensibility; +using NServiceBus.Transport; + +public class ConfigureScenariosForMsmqTransport : IConfigureSupportedScenariosForTestExecution +{ + public IEnumerable UnsupportedScenarioDescriptorTypes { get; } = new[] + { + typeof(AllTransportsWithCentralizedPubSubSupport) + }; +} + +public class ConfigureEndpointMsmqTransport : IConfigureEndpointTestExecution +{ + public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings) + { + queueBindings = configuration.GetSettings().Get(); + var connectionString = settings.Get("Transport.ConnectionString"); + configuration.UseTransport().ConnectionString(connectionString); + return Task.FromResult(0); + } + + public Task Cleanup() + { + var allQueues = MessageQueue.GetPrivateQueuesByMachine("localhost"); + var queuesToBeDeleted = new List(); + + foreach (var messageQueue in allQueues) + { + using (messageQueue) + { + if (queueBindings.ReceivingAddresses.Any(ra => + { + var indexOfAt = ra.IndexOf("@", StringComparison.Ordinal); + if (indexOfAt >= 0) + { + ra = ra.Substring(0, indexOfAt); + } + return messageQueue.QueueName.StartsWith(@"private$\" + ra, StringComparison.OrdinalIgnoreCase); + })) + { + queuesToBeDeleted.Add(messageQueue.Path); + } + } + } + + foreach (var queuePath in queuesToBeDeleted) + { + try + { + MessageQueue.Delete(queuePath); + Console.WriteLine("Deleted '{0}' queue", queuePath); + } + catch (Exception) + { + Console.WriteLine("Could not delete queue '{0}'", queuePath); + } + } + + MessageQueue.ClearConnectionCache(); + + return Task.FromResult(0); + } + + QueueBindings queueBindings; +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ConventionEnforcementTests.cs b/src/NServiceBus.AcceptanceTests/ConventionEnforcementTests.cs new file mode 100644 index 00000000000..68ac9aae85e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ConventionEnforcementTests.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Linq; + using System.Reflection; + using NUnit.Framework; + + [TestFixture] + public class ConventionEnforcementTests : NServiceBusAcceptanceTest + { + [Test] + public void Ensure_all_tests_derive_from_a_common_base_class() + { + var testTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(HasTestMethod); + + var missingBaseClass = testTypes + .Where(t => t.BaseType == null || !typeof(NServiceBusAcceptanceTest).IsAssignableFrom(t)) + .ToList(); + + CollectionAssert.IsEmpty(missingBaseClass, string.Join(",", missingBaseClass)); + } + + static bool HasTestMethod(Type t) + { + return t.GetMethods().Any(m => m.GetCustomAttributes().Any()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/CriticalError/When_registering_custom_critical_error_handler.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/CriticalError/When_registering_custom_critical_error_handler.cs new file mode 100644 index 00000000000..d22dce8a90c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/CriticalError/When_registering_custom_critical_error_handler.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport.CriticalError +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_registering_custom_critical_error_handler : NServiceBusAcceptanceTest + { + [Test] + public Task Critical_error_should_be_raised_inside_delegate() + { + return Scenario.Define() + .WithEndpoint(b => b.When( + (session, context) => session.SendLocal(new MyRequest()))) + .Done(c => c.ExceptionReceived) + .Repeat(r => r.For(Transports.AllAvailable.SingleOrDefault(t => t.Key == "FakeTransport"))) + .Should(c => + { + Assert.AreEqual("Startup task failed to complete.", c.Message); + Assert.AreEqual("ExceptionInBusStarts", c.Exception.Message); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public Exception Exception { get; set; } + public string Message { get; set; } + public bool ExceptionReceived { get; set; } + } + + public class EndpointWithLocalCallback : EndpointConfigurationBuilder + { + public EndpointWithLocalCallback() + { + EndpointSetup(builder => + { + builder.UseTransport() + .RaiseCriticalErrorDuringStartup(new AggregateException("Startup task failed to complete.", new InvalidOperationException("ExceptionInBusStarts"))); + + builder.DefineCriticalErrorAction(errorContext => + { + var aggregateException = (AggregateException) errorContext.Exception; + var context = builder.GetSettings().Get(); + context.Exception = aggregateException.InnerExceptions.First(); + context.Message = errorContext.Error; + context.ExceptionReceived = true; + return Task.FromResult(0); + }); + }); + } + + public class MyRequestHandler : IHandleMessages + { + public Task Handle(MyRequest request, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyRequest : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeDispatcher.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeDispatcher.cs new file mode 100644 index 00000000000..69f36132a5d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeDispatcher.cs @@ -0,0 +1,14 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using System.Threading.Tasks; + using Extensibility; + using Transport; + + class FakeDispatcher : IDispatchMessages + { + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeQueueCreator.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeQueueCreator.cs new file mode 100644 index 00000000000..5236f89626d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeQueueCreator.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using System.Threading.Tasks; + using Transport; + + class FakeQueueCreator : ICreateQueues + { + public Task CreateQueueIfNecessary(QueueBindings queueBindings, string identity) + { + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeReceiver.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeReceiver.cs new file mode 100644 index 00000000000..7ac0c3f5b91 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeReceiver.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using System; + using System.Threading.Tasks; + using Transport; + + class FakeReceiver : IPushMessages + { + public FakeReceiver(Exception throwCritical) + { + this.throwCritical = throwCritical; + } + + public Task Init(Func onMessage, Func> onError, NServiceBus.CriticalError criticalError, PushSettings settings) + { + this.criticalError = criticalError; + return Task.FromResult(0); + } + + public void Start(PushRuntimeSettings limitations) + { + if (throwCritical != null) + { + criticalError.Raise(throwCritical.Message, throwCritical); + } + } + + public Task Stop() + { + return Task.FromResult(0); + } + + NServiceBus.CriticalError criticalError; + Exception throwCritical; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransport.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransport.cs new file mode 100644 index 00000000000..6bf2368f85e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransport.cs @@ -0,0 +1,17 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using Settings; + using Transport; + + public class FakeTransport : TransportDefinition + { + public override bool RequiresConnectionString => false; + + public override string ExampleConnectionStringForErrorMessage => null; + + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + return new FakeTransportInfrastructure(settings); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportInfrastructure.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportInfrastructure.cs new file mode 100644 index 00000000000..da3597f98c4 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportInfrastructure.cs @@ -0,0 +1,64 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NServiceBus.Routing; + using Settings; + using Transport; + + public class FakeTransportInfrastructure : TransportInfrastructure + { + public FakeTransportInfrastructure(ReadOnlySettings settings) + { + this.settings = settings; + } + + public override IEnumerable DeliveryConstraints { get; } = Enumerable.Empty(); + + public override TransportTransactionMode TransactionMode + { + get + { + TransportTransactionMode supportedTransactionMode; + + if (settings.TryGet("FakeTransport.SupportedTransactionMode", out supportedTransactionMode)) + { + return supportedTransactionMode; + } + + return TransportTransactionMode.TransactionScope; + } + } + + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + + public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) + { + return instance; + } + + public override string ToTransportAddress(LogicalAddress logicalAddress) + { + return logicalAddress.ToString(); + } + + public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() + { + return new TransportReceiveInfrastructure(() => new FakeReceiver(settings.GetOrDefault()), () => new FakeQueueCreator(), () => Task.FromResult(StartupCheckResult.Success)); + } + + public override TransportSendInfrastructure ConfigureSendInfrastructure() + { + return new TransportSendInfrastructure(() => new FakeDispatcher(), () => Task.FromResult(StartupCheckResult.Success)); + } + + public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() + { + throw new NotImplementedException(); + } + + ReadOnlySettings settings; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportSetingsExtensions.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportSetingsExtensions.cs new file mode 100644 index 00000000000..9411daf8854 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/FakeTransportSetingsExtensions.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport +{ + using System; + using Configuration.AdvanceExtensibility; + + public static class FakeTransportSetingsExtensions + { + public static TransportExtensions RaiseCriticalErrorDuringStartup(this TransportExtensions transportExtensions, Exception exception) + { + transportExtensions.GetSettings().Set(exception); + + return transportExtensions; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/FakeTransport/ProcessingOptimizations/When_using_concurrency_limit.cs b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/ProcessingOptimizations/When_using_concurrency_limit.cs new file mode 100644 index 00000000000..fe3fc518799 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/FakeTransport/ProcessingOptimizations/When_using_concurrency_limit.cs @@ -0,0 +1,115 @@ +namespace NServiceBus.AcceptanceTests.Core.FakeTransport.ProcessingOptimizations +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using NServiceBus.Routing; + using NUnit.Framework; + using Settings; + using Transport; + using CriticalError = NServiceBus.CriticalError; + + public class When_using_concurrency_limit : NServiceBusAcceptanceTest + { + [Test] + public Task Should_pass_it_to_the_transport() + { + return Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => c.LimitMessageProcessingConcurrencyTo(10))) + .Done(c => c.EndpointsStarted) + .Run(); + + //Assert in FakeReceiver.Start + } + + class ThrottledEndpoint : EndpointConfigurationBuilder + { + public ThrottledEndpoint() + { + EndpointSetup(c => c.UseTransport()); + } + } + + class FakeReceiver : IPushMessages + { + public Task Init(Func onMessage, Func> onError, CriticalError criticalError, PushSettings settings) + { + return Task.FromResult(0); + } + + public void Start(PushRuntimeSettings limitations) + { + Assert.AreEqual(10, limitations.MaxConcurrency); + } + + public Task Stop() + { + return Task.FromResult(0); + } + } + + class FakeQueueCreator : ICreateQueues + { + public Task CreateQueueIfNecessary(QueueBindings queueBindings, string identity) + { + return Task.FromResult(0); + } + } + + class FakeDispatcher : IDispatchMessages + { + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + return Task.FromResult(0); + } + } + + class FakeTransport : TransportDefinition + { + public override string ExampleConnectionStringForErrorMessage => null; + + public override bool RequiresConnectionString => false; + + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + return new FakeTransportInfrastructure(); + } + } + + class FakeTransportInfrastructure : TransportInfrastructure + { + public override IEnumerable DeliveryConstraints { get; } = Enumerable.Empty(); + public override TransportTransactionMode TransactionMode { get; } = TransportTransactionMode.None; + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + + public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) + { + return instance; + } + + public override string ToTransportAddress(LogicalAddress logicalAddress) + { + return logicalAddress.ToString(); + } + + public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() + { + return new TransportReceiveInfrastructure(() => new FakeReceiver(), () => new FakeQueueCreator(), () => Task.FromResult(StartupCheckResult.Success)); + } + + public override TransportSendInfrastructure ConfigureSendInfrastructure() + { + return new TransportSendInfrastructure(() => new FakeDispatcher(), () => Task.FromResult(StartupCheckResult.Success)); + } + + public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Feature/When_feature_startup_task_fails.cs b/src/NServiceBus.AcceptanceTests/Core/Feature/When_feature_startup_task_fails.cs new file mode 100644 index 00000000000..134f66ab593 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Feature/When_feature_startup_task_fails.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.AcceptanceTests.Core.Feature +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_feature_startup_task_fails : NServiceBusAcceptanceTest + { + [Test] + public void Should_not_start_endpoint() + { + var exception = Assert.ThrowsAsync(() => + Scenario.Define() + .WithEndpoint() + .Run()); + + Assert.That(exception.InnerException.InnerException, Is.TypeOf()); + } + + class EndpointWithStartupTask : EndpointConfigurationBuilder + { + public EndpointWithStartupTask() + { + EndpointSetup(c => c.EnableFeature()); + } + + class FeatureWithStartupTask : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(new FailingStartupTask()); + } + } + + class FailingStartupTask : FeatureStartupTask + { + protected override Task OnStart(IMessageSession session) + { + throw new SimulatedException(); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/LegacyRetries/When_legacy_retries_left_in_retry_queue.cs b/src/NServiceBus.AcceptanceTests/Core/LegacyRetries/When_legacy_retries_left_in_retry_queue.cs new file mode 100644 index 00000000000..4a3fbcde65a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/LegacyRetries/When_legacy_retries_left_in_retry_queue.cs @@ -0,0 +1,75 @@ +namespace NServiceBus.AcceptanceTests.Core.LegacyRetries +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_legacy_retries_left_in_retry_queue : NServiceBusAcceptanceTest + { + [Test] + public async Task they_are_moved_to_main_queue_and_processed() + { + var endpointName = AcceptanceTesting.Customization.Conventions.EndpointNamingConvention(typeof(RetryEndpoint)); + var retryQueueAddress = $"{endpointName}.Retries"; + + var sendOptions = new SendOptions(); + + sendOptions.SetDestination(retryQueueAddress); + + sendOptions.SetHeader("NServiceBus.ExceptionInfo.Reason", "reason"); + sendOptions.SetHeader("NServiceBus.FailedQ", "queue"); + sendOptions.SetHeader("NServiceBus.TimeOfFailure", "time"); + sendOptions.SetHeader("NServiceBus.OriginalId", "id"); + + var context = await Scenario.Define() + .WithEndpoint(c => c.When( + (s, ctx) => s.Send(new LegacyRetryMessage {TestRunId = ctx.TestRunId}, sendOptions))) + .Done(c => c.DeliveredMessageHeaders != null) + .Run(); + + var headers = context.DeliveredMessageHeaders; + + Assert.IsFalse(headers.ContainsKey("NServiceBus.ExceptionInfo.Reason")); + Assert.IsFalse(headers.ContainsKey("NServiceBus.FailedQ")); + Assert.IsFalse(headers.ContainsKey("NServiceBus.TimeOfFailure")); + Assert.IsFalse(headers.ContainsKey("NServiceBus.OriginalId")); + } + + + class TestContext : ScenarioContext + { + public IReadOnlyDictionary DeliveredMessageHeaders { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup().AddMapping(typeof(RetryEndpoint)); + } + + class LegacyRetriesMessages : IHandleMessages + { + public TestContext TestContext { get; set; } + + public Task Handle(LegacyRetryMessage legacyRetryMessage, IMessageHandlerContext context) + { + if (legacyRetryMessage.TestRunId == TestContext.TestRunId) + { + TestContext.DeliveredMessageHeaders = context.MessageHeaders; + } + + return Task.FromResult(0); + } + } + } + + public class LegacyRetryMessage : IMessage + { + public Guid TestRunId { get; set; } + } + } +} diff --git a/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_disabling_durable_messages_on_durable_transport.cs b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_disabling_durable_messages_on_durable_transport.cs new file mode 100644 index 00000000000..519a36b43d9 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_disabling_durable_messages_on_durable_transport.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.AcceptanceTests.Core.MessageDurability +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using FakeTransport; + using NUnit.Framework; + + public class When_disabling_durable_messages_on_durable_transport : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_exception_at_startup() + { + var exception = Assert.ThrowsAsync(() => Scenario.Define() + .WithEndpoint(c => c + .When(e => e.SendLocal(new RegularMessage()))) + .Done(c => c.EndpointsStarted) + .Run()); + + Assert.That(exception.InnerException.InnerException, Is.TypeOf()); + Assert.That(exception.InnerException.InnerException.Message, Does.Contain("The configured transport does not support non-durable messages but some messages have been configured to be non-durable")); + } + + class EndpointDisablingDurableMessages : EndpointConfigurationBuilder + { + public EndpointDisablingDurableMessages() + { + EndpointSetup(c => + { + c.DisableDurableMessages(); + c.UseTransport(); + }); + } + } + + class RegularMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message.cs b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message.cs new file mode 100644 index 00000000000..a1ce3f0a496 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message.cs @@ -0,0 +1,53 @@ +namespace NServiceBus.AcceptanceTests.Core.MessageDurability +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_a_non_durable_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_be_available_as_a_header_on_receiver() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Run(TimeSpan.FromSeconds(10)); + + Assert.IsTrue(context.NonDurabilityHeader, "Message should be flagged as non-durable"); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public bool NonDurabilityHeader { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + TestContext.NonDurabilityHeader = bool.Parse(context.MessageHeaders[Headers.NonDurableMessage]); + TestContext.WasCalled = true; + return Task.FromResult(0); + } + } + } + + [Express] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message_with_conventions.cs b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message_with_conventions.cs new file mode 100644 index 00000000000..6f6a5ee66bc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_sending_a_non_durable_message_with_conventions.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.AcceptanceTests.Core.MessageDurability +{ + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_a_non_durable_message_with_conventions : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_be_available_as_a_header_on_receiver() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MyExpressMessage()))) + .WithEndpoint() + .Done(c => c.WasCalled || c.FailedMessages.Any()) + .Run(); + + Assert.IsTrue(context.NonDurabilityHeader, "Message should be flagged as non-durable"); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public bool NonDurabilityHeader { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyExpressMessage).FullName) + .DefiningExpressMessagesAs(t => t.Name.Contains("Express")); + }).AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyExpressMessage).FullName) + .DefiningExpressMessagesAs(t => t.Name.Contains("Express")); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyExpressMessage message, IMessageHandlerContext context) + { + TestContext.NonDurabilityHeader = bool.Parse(context.MessageHeaders[Headers.NonDurableMessage]); + TestContext.WasCalled = true; + return Task.FromResult(0); + } + } + } + + public class MyExpressMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_using_non_durable_messages_on_durable_only_transport.cs b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_using_non_durable_messages_on_durable_only_transport.cs new file mode 100644 index 00000000000..5777beb567b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/MessageDurability/When_using_non_durable_messages_on_durable_only_transport.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.AcceptanceTests.Core.MessageDurability +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using FakeTransport; + using NUnit.Framework; + + public class When_using_non_durable_messages_on_durable_only_transport : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_exception_when_sending() + { + var exception = Assert.ThrowsAsync(() => Scenario.Define() + .WithEndpoint(c => c + .When(e => e.SendLocal(new NonDurableMessage()))) + .Done(c => c.EndpointsStarted) + .Run()); + + Assert.That(exception.InnerException.InnerException, Is.TypeOf()); + Assert.That(exception.InnerException.InnerException.Message, Does.Contain("The configured transport does not support non-durable messages but some messages have been configured to be non-durable")); + } + + class EndpointUsingNonDurableMessage : EndpointConfigurationBuilder + { + public EndpointUsingNonDurableMessage() + { + EndpointSetup(c => c.UseTransport()); + } + } + + [Express] + class NonDurableMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Outbox/When_dispatching_transport_operations.cs b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_dispatching_transport_operations.cs new file mode 100644 index 00000000000..4fdf3c32976 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Outbox/When_dispatching_transport_operations.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.AcceptanceTests.Core.Outbox +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_dispatching_transport_operations : NServiceBusAcceptanceTest + { + [Test] + public Task Should_honor_all_delivery_options() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new PlaceOrder()))) + .Done(c => c.DispatchedMessageReceived) + .Repeat(r => r.For()) + .Should(context => + { + Assert.AreEqual(TimeSpan.FromMinutes(1), TimeSpan.Parse(context.HeadersOnDispatchedMessage[Headers.TimeToBeReceived]), "Should honor the TTBR"); + Assert.True(bool.Parse(context.HeadersOnDispatchedMessage[Headers.NonDurableMessage]), "Should honor the durability"); + }) + .Run(TimeSpan.FromSeconds(20)); + } + + public class Context : ScenarioContext + { + public bool DispatchedMessageReceived { get; set; } + public IReadOnlyDictionary HeadersOnDispatchedMessage { get; set; } + } + + public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder + { + public NonDtcReceivingEndpoint() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + }); + } + + class PlaceOrderHandler : IHandleMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + return context.SendLocal(new MessageToDispatch()); + } + } + + class MessageToDispatchHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToDispatch message, IMessageHandlerContext context) + { + TestContext.HeadersOnDispatchedMessage = context.MessageHeaders; + TestContext.DispatchedMessageReceived = true; + return Task.FromResult(0); + } + } + } + + public class PlaceOrder : ICommand + { + } + + [TimeToBeReceived("00:01:00")] + [Express] + class MessageToDispatch : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_ISynchronizationContext.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_ISynchronizationContext.cs new file mode 100644 index 00000000000..d43ef1071c7 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_provide_ISynchronizationContext.cs @@ -0,0 +1,88 @@ +namespace NServiceBus.AcceptanceTests.Core.Persistence +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using NServiceBus.Persistence; + using NUnit.Framework; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + public class When_a_persistence_does_not_provide_ISynchronizationContext : NServiceBusAcceptanceTest + { + // Run this test twice to ensure that the NoOpCompletableSynchronizedStorageSession's IDisposable method + // is not altered by Fody to throw an ObjectDisposedException if it was disposed + [Test, Repeat(2)] + public Task ReceiveFeature_should_work_without_ISynchronizedStorage() + { + return Scenario.Define() + .WithEndpoint(e => e.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.MessageReceived) + .Run(); + } + + class InMemoryNoSyncContextPersistence : PersistenceDefinition + { + public InMemoryNoSyncContextPersistence() + { + Supports(s => { }); + Supports(s => { }); + Supports(s => { }); + } + } + + class NoOpISubscriptionStorage : ISubscriptionStorage + { + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) + { + return Task.FromResult>(null); + } + + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + return Task.FromResult(0); + } + + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + return Task.FromResult(0); + } + } + + class NoSyncEndpoint : EndpointConfigurationBuilder + { + public NoSyncEndpoint() + { + EndpointSetup(c => + { + c.RegisterComponents(container => container.ConfigureComponent(DependencyLifecycle.SingleInstance)); + c.UsePersistence(); + }); + } + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.MessageReceived = true; + + return Task.FromResult(0); + } + } + + public class Context : ScenarioContext + { + public bool NotSet { get; set; } + public bool MessageReceived { get; set; } + } + + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_outbox.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_outbox.cs new file mode 100644 index 00000000000..f007261919d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_outbox.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.AcceptanceTests.Core.Persistence +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Persistence; + using NUnit.Framework; + + public class When_a_persistence_does_not_support_outbox : NServiceBusAcceptanceTest + { + [Test] + public void should_throw_exception() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint(e => e.When(b => Task.FromResult(0))) + .Run(); + }, Throws.Exception.InnerException.InnerException.With.Message.Contains("DisableFeature()")); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + + c.GetSettings().Set("DisableOutboxTransportCheck", true); + c.EnableOutbox(); + }); + } + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_saga.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_saga.cs new file mode 100644 index 00000000000..d78390b31d4 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_saga.cs @@ -0,0 +1,69 @@ +namespace NServiceBus.AcceptanceTests.Core.Persistence +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Persistence; + using NUnit.Framework; + + public class When_a_persistence_does_not_support_saga : NServiceBusAcceptanceTest + { + [Test] + public void should_throw_exception() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint(e => e.When(b => b.SendLocal(new StartSaga()))) + .Done(c => c.MessageReceived) + .Run(); + }, Throws.Exception.InnerException.InnerException.With.Message.Contains("DisableFeature()")); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + }); + } + } + + public class PersistenceSaga : Saga, + IAmStartedByMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSaga message, IMessageHandlerContext context) + { + TestContext.MessageReceived = true; + MarkAsComplete(); + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + + public class PersistenceSagaData : ContainSagaData + { + public virtual Guid DataId { get; set; } + } + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + + public class StartSaga : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_subscriptions.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_subscriptions.cs new file mode 100644 index 00000000000..2b380ac61a1 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_subscriptions.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.AcceptanceTests.Core.Persistence +{ + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Persistence; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_a_persistence_does_not_support_subscriptions : NServiceBusAcceptanceTest + { + [Test] + public void should_throw_exception() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint(e => e.When(b => b.Subscribe())) + .Repeat(r => r.For()) + .Run(); + }, Throws.Exception.InnerException.InnerException.With.Message.Contains("DisableFeature()")); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + }); + } + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_timeouts.cs b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_timeouts.cs new file mode 100644 index 00000000000..0ec933cfc9e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Persistence/When_a_persistence_does_not_support_timeouts.cs @@ -0,0 +1,46 @@ +namespace NServiceBus.AcceptanceTests.Core.Persistence +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Persistence; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_a_persistence_does_not_support_timeouts : NServiceBusAcceptanceTest + { + [Test] + public void should_throw_exception() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint(e => e.When(b => Task.FromResult(0))) + .Repeat(r => r.For()) + .Run(); + }, Throws.Exception.InnerException.InnerException.With.Message.Contains("DisableFeature()")); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + c.UsePersistence(); + + c.EnableFeature(); + }); + } + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/Routing/AutomaticSubscriptions/When_handling_local_event.cs b/src/NServiceBus.AcceptanceTests/Core/Routing/AutomaticSubscriptions/When_handling_local_event.cs new file mode 100644 index 00000000000..1d2d0dfd267 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Routing/AutomaticSubscriptions/When_handling_local_event.cs @@ -0,0 +1,80 @@ +namespace NServiceBus.AcceptanceTests.Core.Routing.AutomaticSubscriptions +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTests.Routing; + using EndpointTemplates; + using NUnit.Framework; + + public class When_handling_local_event : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_autosubscribe_to_event() + { + var ctx = await Scenario.Define(x => x.Id = Guid.NewGuid()) + .WithEndpoint(b => b + .When((session, context) => + { + if (context.HasNativePubSubSupport) + { + context.EventSubscribed = true; + } + return Task.FromResult(0); + }) + .When(c => c.EventSubscribed || c.HasNativePubSubSupport, (session, context) => session.Publish(new Event { ContextId = context.Id }))) + .Done(c => c.GotEvent) + .Run().ConfigureAwait(false); + + Assert.True(ctx.GotEvent); + } + + public class Context : ScenarioContext + { + public Guid Id { get; set; } + public bool GotEvent { get; set; } + public bool EventSubscribed { get; set; } + } + + public class PublisherAndSubscriber : EndpointConfigurationBuilder + { + public PublisherAndSubscriber() + { + EndpointSetup(b => + { + // Make sure the subscription message isn't purged on startup + b.PurgeOnStartup(true); + b.OnEndpointSubscribed((s, context) => + { + if (s.MessageType == typeof(Event).AssemblyQualifiedName) + { + context.EventSubscribed = true; + } + }); + }) + .AddMapping(typeof(PublisherAndSubscriber)); + } + + public class EventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Event @event, IMessageHandlerContext context) + { + if (@event.ContextId != Context.Id) + { + return Task.FromResult(0); + } + Context.GotEvent = true; + + return Task.FromResult(0); + } + } + } + + public class Event : IEvent + { + public Guid ContextId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_transactionscope_enabled.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_transactionscope_enabled.cs new file mode 100644 index 00000000000..5d01fbdba12 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_transactionscope_enabled.cs @@ -0,0 +1,66 @@ +namespace NServiceBus.AcceptanceTests.Core.UnitOfWork.TransactionScope +{ + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_transactionscope_enabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_wrap_the_handlers_in_a_scope() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.Done) + .Run(); + + Assert.True(context.AmbientTransactionPresent, "There should be a ambient transaction present"); + Assert.AreEqual(context.IsolationLevel, IsolationLevel.RepeatableRead, "There should be a ambient transaction present"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool AmbientTransactionPresent { get; set; } + public IsolationLevel IsolationLevel { get; set; } + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup((c, r) => + { + c.UseTransport(r.GetTransportType()) + .Transactions(TransportTransactionMode.ReceiveOnly); + c.UnitOfWork() + .WrapHandlersInATransactionScope( + isolationLevel: IsolationLevel.RepeatableRead); + }); + } + + class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (Transaction.Current != null) + { + Context.AmbientTransactionPresent = Transaction.Current != null; + Context.IsolationLevel = Transaction.Current.IsolationLevel; + } + Context.Done = true; + + return Task.FromResult(0); + } + } + } + + class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_default_transaction_mode.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_default_transaction_mode.cs new file mode 100644 index 00000000000..32637af3128 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_default_transaction_mode.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.AcceptanceTests.Core.UnitOfWork.TransactionScope +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using FakeTransport; + using NUnit.Framework; + + public class When_used_with_default_transaction_mode : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_work() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.GetSettings().Set("FakeTransport.SupportedTransactionMode", TransportTransactionMode.ReceiveOnly); + c.UseTransport(); + c.UnitOfWork() + .WrapHandlersInATransactionScope(); + })) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.True(context.EndpointsStarted); + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_transport_scopes.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_transport_scopes.cs new file mode 100644 index 00000000000..a8ed0aacec3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_used_with_transport_scopes.cs @@ -0,0 +1,38 @@ +namespace NServiceBus.AcceptanceTests.Core.UnitOfWork.TransactionScope +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using FakeTransport; + using NUnit.Framework; + + public class When_used_with_transport_scopes : NServiceBusAcceptanceTest + { + [Test] + public void Should_blow_up() + { + var aex = Assert.ThrowsAsync(async () => + { + await Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => + { + c.UseTransport() + .Transactions(TransportTransactionMode.TransactionScope); + c.UnitOfWork() + .WrapHandlersInATransactionScope(); + })) + .Run(); + }); + + Assert.True(aex.InnerException.InnerException.Message.Contains("A Transaction scope unit of work can't be used when the transport already uses a scope")); + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_using_timeout_greater_than_machine_max.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_using_timeout_greater_than_machine_max.cs new file mode 100644 index 00000000000..a8128fef1fe --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/TransactionScope/When_using_timeout_greater_than_machine_max.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.AcceptanceTests.Core.UnitOfWork.TransactionScope +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_using_timeout_greater_than_machine_max : NServiceBusAcceptanceTest + { + [Test] + public void Should_blow_up() + { + var aex = Assert.ThrowsAsync(async () => + { + await Scenario.Define() + .WithEndpoint() + .Run(); + }); + + Assert.True(aex.InnerException.InnerException.Message.Contains("Timeout requested is longer than the maximum value for this machine")); + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup((c, r) => + { + c.UseTransport(r.GetTransportType()) + .Transactions(TransportTransactionMode.ReceiveOnly); + c.UnitOfWork() + .WrapHandlersInATransactionScope(TimeSpan.FromHours(1)); + }); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_a_subscription_message_arrives.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_a_subscription_message_arrives.cs new file mode 100644 index 00000000000..ed7ee9e19e5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_a_subscription_message_arrives.cs @@ -0,0 +1,68 @@ +namespace NServiceBus.AcceptanceTests.Core.UnitOfWork +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.UnitOfWork; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_a_subscription_message_arrives : NServiceBusAcceptanceTest + { + [Test] + public Task Should_invoke_uow() + { + return Scenario.Define() + .WithEndpoint() + .Done(c => c.UowWasCalled) + .Repeat(b => b.For()) + .Should(c => Assert.True(c.UowWasCalled)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool UowWasCalled { get; set; } + } + + public class UOWEndpoint : EndpointConfigurationBuilder + { + public UOWEndpoint() + { + EndpointSetup(c => c.RegisterComponents(container => container.ConfigureComponent(DependencyLifecycle.InstancePerCall))) + .AddMapping(typeof(UOWEndpoint)); + } + + class MyUow : IManageUnitsOfWork + { + public Context Context { get; set; } + + public Task Begin() + { + Context.UowWasCalled = true; + return Task.FromResult(0); + } + + public Task End(Exception ex = null) + { + return Task.FromResult(0); + } + } + + public class DummyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyMessage : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Correlation/When_replying_to_received_message_without_correlationid.cs b/src/NServiceBus.AcceptanceTests/Correlation/When_replying_to_received_message_without_correlationid.cs new file mode 100644 index 00000000000..20649ee712e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Correlation/When_replying_to_received_message_without_correlationid.cs @@ -0,0 +1,92 @@ +namespace NServiceBus.AcceptanceTests.Correlation +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_replying_to_received_message_without_correlationid : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_the_incoming_message_id_as_the_correlation_id() + { + const string mycustomid = "mycustomid"; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => + { + var sendOptions = new SendOptions(); + sendOptions.RouteToThisEndpoint(); + sendOptions.SetMessageId(mycustomid); + return session.Send(new MyRequest(), sendOptions); + })) + .Done(c => c.GotResponse) + .Run(); + + Assert.AreEqual(mycustomid, context.CorrelationIdReceived, "Correlation id should match MessageId"); + } + + public class Context : ScenarioContext + { + public bool GotResponse { get; set; } + public string CorrelationIdReceived { get; set; } + } + + public class CorrelationEndpoint : EndpointConfigurationBuilder + { + public CorrelationEndpoint() + { + EndpointSetup(c => c.RegisterComponents( + components => { components.ConfigureComponent(DependencyLifecycle.InstancePerCall); })); + } + + public class MyRequestHandler : IHandleMessages + { + public Task Handle(MyRequest message, IMessageHandlerContext context) + { + return context.Reply(new MyResponse()); + } + } + + public class MyResponseHandler : IHandleMessages + { + public MyResponseHandler(Context context) + { + this.context = context; + } + + public Task Handle(MyResponse message, IMessageHandlerContext c) + { + context.CorrelationIdReceived = c.MessageHeaders[Headers.CorrelationId]; + context.GotResponse = true; + + return Task.FromResult(0); + } + + readonly Context context; + } + + class RemoveCorrelationId : IMutateIncomingTransportMessages + { + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + if (context.Headers[Headers.MessageIntent] != MessageIntentEnum.Reply.ToString()) + { + context.Headers.Remove(Headers.CorrelationId); + } + + return Task.FromResult(0); + } + } + } + + public class MyRequest : IMessage + { + } + + public class MyResponse : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Correlation/When_sending_with_no_correlation_id.cs b/src/NServiceBus.AcceptanceTests/Correlation/When_sending_with_no_correlation_id.cs new file mode 100644 index 00000000000..5632eaf116a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Correlation/When_sending_with_no_correlation_id.cs @@ -0,0 +1,56 @@ +namespace NServiceBus.AcceptanceTests.Correlation +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_with_no_correlation_id : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_the_message_id_as_the_correlation_id() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MyRequest()))) + .Done(c => c.GotRequest) + .Run(); + + Assert.AreEqual(context.MessageIdReceived, context.CorrelationIdReceived, "Correlation id should match MessageId"); + } + + public class Context : ScenarioContext + { + public string MessageIdReceived { get; set; } + public bool GotRequest { get; set; } + public string CorrelationIdReceived { get; set; } + } + + public class CorrelationEndpoint : EndpointConfigurationBuilder + { + public CorrelationEndpoint() + { + EndpointSetup(); + } + + public class MyResponseHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyRequest message, IMessageHandlerContext context) + { + TestContext.CorrelationIdReceived = context.MessageHeaders[Headers.CorrelationId]; + TestContext.MessageIdReceived = context.MessageId; + TestContext.GotRequest = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyRequest : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Correlation/When_using_a_custom_correlation_id.cs b/src/NServiceBus.AcceptanceTests/Correlation/When_using_a_custom_correlation_id.cs new file mode 100644 index 00000000000..aa5bca9a79c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Correlation/When_using_a_custom_correlation_id.cs @@ -0,0 +1,66 @@ +namespace NServiceBus.AcceptanceTests.Correlation +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_using_a_custom_correlation_id : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_the_given_id_as_the_transport_level_correlation_id() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => + { + var options = new SendOptions(); + + options.SetCorrelationId(CorrelationId); + options.RouteToThisEndpoint(); + + return session.Send(new MessageWithCustomCorrelationId(), options); + })) + .Done(c => c.GotRequest) + .Run(); + + Assert.AreEqual(CorrelationId, context.CorrelationIdReceived, "Correlation ids should match"); + } + + static string CorrelationId = "my_custom_correlation_id"; + + public class Context : ScenarioContext + { + public bool GotRequest { get; set; } + + public string CorrelationIdReceived { get; set; } + } + + public class CorrelationEndpoint : EndpointConfigurationBuilder + { + public CorrelationEndpoint() + { + EndpointSetup(); + } + + public class SendMessageWithCorrelationHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageWithCustomCorrelationId message, IMessageHandlerContext context) + { + TestContext.CorrelationIdReceived = context.MessageHeaders[Headers.CorrelationId]; + + TestContext.GotRequest = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MessageWithCustomCorrelationId : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/CriticalError/When_raising_critical_error.cs b/src/NServiceBus.AcceptanceTests/CriticalError/When_raising_critical_error.cs new file mode 100644 index 00000000000..b20b6933217 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/CriticalError/When_raising_critical_error.cs @@ -0,0 +1,153 @@ +namespace NServiceBus.AcceptanceTests.CriticalError +{ + using System; + using System.Collections.Concurrent; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using CriticalError = NServiceBus.CriticalError; + + public class When_raising_critical_error : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_trigger_critical_error_action_when_raised_from_handler() + { + var exceptions = new ConcurrentDictionary(); + + Func addCritical = criticalContext => + { + exceptions.TryAdd(criticalContext.Error, criticalContext.Exception); + return Task.FromResult(0); + }; + + await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(config => { config.DefineCriticalErrorAction(addCritical); }); + + b.When((session, c) => + { + c.ContextId = Guid.NewGuid().ToString(); + return session.SendLocal(new Message + { + ContextId = c.ContextId + }); + }); + }) + .Done(c => c.CriticalErrorsRaised > 0) + .Run(); + + Assert.AreEqual(1, exceptions.Keys.Count); + } + + [Test] + public async Task Should_call_critical_error_action_for_every_error_that_occurred_before_startup() + { + var exceptions = new ConcurrentDictionary(); + + Func addCritical = criticalContext => + { + exceptions.TryAdd(criticalContext.Error, criticalContext.Exception); + return Task.FromResult(0); + }; + + var context = await Scenario.Define() + .WithEndpoint(b => { b.CustomConfig(config => { config.DefineCriticalErrorAction(addCritical); }); }) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.AreEqual(2, context.CriticalErrorsRaised); + Assert.AreEqual(exceptions.Keys.Count, context.CriticalErrorsRaised); + } + + public class TestContext : ScenarioContext + { + public string ContextId { get; set; } + public int CriticalErrorsRaised { get; set; } + } + + public class EndpointWithCriticalError : EndpointConfigurationBuilder + { + public EndpointWithCriticalError() + { + EndpointSetup(); + } + + public class CriticalHandler : IHandleMessages + { + public CriticalHandler(CriticalError criticalError, TestContext testContext) + { + this.criticalError = criticalError; + this.testContext = testContext; + } + + public Task Handle(Message request, IMessageHandlerContext context) + { + if (testContext.ContextId == request.ContextId) + { + criticalError.Raise("a critical error", new SimulatedException()); + testContext.CriticalErrorsRaised++; + } + + return Task.FromResult(0); + } + + CriticalError criticalError; + TestContext testContext; + } + } + + class CriticalErrorStartup : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new CriticalErrorStartupFeatureTask(b.Build(), b.Build())); + } + + class CriticalErrorStartupFeatureTask : FeatureStartupTask + { + public CriticalErrorStartupFeatureTask(CriticalError criticalError, TestContext testContext) + { + this.criticalError = criticalError; + this.testContext = testContext; + } + + protected override Task OnStart(IMessageSession session) + { + criticalError.Raise("critical error 1", new SimulatedException()); + testContext.CriticalErrorsRaised++; + + criticalError.Raise("critical error 2", new SimulatedException()); + testContext.CriticalErrorsRaised++; + + return Task.FromResult(0); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + + readonly TestContext testContext; + + CriticalError criticalError; + } + } + + public class EndpointWithCriticalErrorStartup : EndpointConfigurationBuilder + { + public EndpointWithCriticalErrorStartup() + { + EndpointSetup(c => c.EnableFeature()); + } + } + + [Serializable] + public class Message : IMessage + { + public string ContextId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/CriticalError/When_registering_a_custom_criticalErrorHandler.cs b/src/NServiceBus.AcceptanceTests/CriticalError/When_registering_a_custom_criticalErrorHandler.cs deleted file mode 100644 index 744ef6246f8..00000000000 --- a/src/NServiceBus.AcceptanceTests/CriticalError/When_registering_a_custom_criticalErrorHandler.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace NServiceBus.AcceptanceTests.CriticalError -{ - using System; - using System.Linq; - using AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - using ScenarioDescriptors; - using IMessage = NServiceBus.IMessage; - - public class When_registering_a_custom_criticalErrorHandler : NServiceBusAcceptanceTest - { - [Test] - public void Critical_error_should_be_raised_inside_delegate() - { - Scenario.Define() - .WithEndpoint(b => b.Given( - (bus, context) => bus.SendLocal(new MyRequest()))) - .AllowExceptions(exception => true) - .Done(c => c.ExceptionReceived) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.AreEqual("Startup task failed to complete.", c.Message); - Assert.AreEqual("ExceptionInBusStarts", c.Exception.Message); - }) - .Run(new TimeSpan(1, 1, 1)); - } - - public class Context : ScenarioContext - { - public Exception Exception { get; set; } - public string Message { get; set; } - public bool ExceptionReceived { get; set; } - } - - public class EndpointWithLocalCallback : EndpointConfigurationBuilder - { - public static Context Context { get; set; } - public EndpointWithLocalCallback() - { - EndpointSetup(builder => builder.DefineCriticalErrorAction((s, exception) => - { - var aggregateException = (AggregateException) exception; - aggregateException = (AggregateException)aggregateException.InnerExceptions.First(); - Context.Exception = aggregateException.InnerExceptions.First(); - Context.Message = s; - Context.ExceptionReceived = true; - })); - } - - public class MyRequestHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyRequest request) - { - } - } - - - class AfterConfigIsComplete : IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - public void Start() - { - EndpointWithLocalCallback.Context = Context; - throw new Exception("ExceptionInBusStarts"); - } - - public void Stop() - { - } - } - } - - [Serializable] - public class MyRequest : IMessage{} - } -} diff --git a/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties.cs b/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties.cs index e5197b50f56..ca7fbff9ef9 100644 --- a/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties.cs +++ b/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties.cs @@ -1,41 +1,48 @@ namespace NServiceBus.AcceptanceTests.DataBus { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; using NUnit.Framework; - public class When_sending_databus_properties:NServiceBusAcceptanceTest + public class When_sending_databus_properties : NServiceBusAcceptanceTest { - static byte[] PayloadToSend = new byte[1024 * 1024 * 10]; - [Test] - public void Should_receive_the_message_the_largeproperty_correctly() + public async Task Should_receive_messages_with_largepayload_correctly() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus=> bus.Send(new MyMessageWithLargePayload - { - Payload = new DataBusProperty(PayloadToSend) - }))) - .WithEndpoint() - .Done(c => c.ReceivedPayload != null) - .Run(); + var payloadToSend = new byte[PayloadSize]; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.Send(new MyMessageWithLargePayload + { + Payload = new DataBusProperty(payloadToSend) + }))) + .WithEndpoint() + .Done(c => c.ReceivedPayload != null) + .Run(); - Assert.AreEqual(PayloadToSend, context.ReceivedPayload, "The large payload should be marshalled correctly using the databus"); + Assert.AreEqual(payloadToSend, context.ReceivedPayload, "The large payload should be marshalled correctly using the databus"); } + const int PayloadSize = 100; + public class Context : ScenarioContext { public byte[] ReceivedPayload { get; set; } } - public class Sender : EndpointConfigurationBuilder { public Sender() { - EndpointSetup(builder => builder.UseDataBus().BasePath(@".\databus\sender")) - .AddMapping(typeof (Receiver)); + EndpointSetup(builder => + { + builder.UseDataBus().BasePath(@".\databus\sender"); + builder.UseSerialization(); + }) + .AddMapping(typeof(Receiver)); } } @@ -43,16 +50,35 @@ public class Receiver : EndpointConfigurationBuilder { public Receiver() { - EndpointSetup(builder => builder.UseDataBus().BasePath(@".\databus\sender")); + EndpointSetup(builder => + { + builder.UseDataBus().BasePath(@".\databus\sender"); + builder.UseSerialization(); + builder.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + }); } public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessageWithLargePayload messageWithLargePayload) + public Task Handle(MyMessageWithLargePayload messageWithLargePayload, IMessageHandlerContext context) { Context.ReceivedPayload = messageWithLargePayload.Payload.Value; + + return Task.FromResult(0); + } + } + + public class Mutator : IMutateIncomingTransportMessages + { + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + if (context.Body.Length > PayloadSize) + { + throw new Exception(); + } + return Task.FromResult(0); } } } @@ -63,4 +89,4 @@ public class MyMessageWithLargePayload : ICommand public DataBusProperty Payload { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties_with_unobtrusive.cs b/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties_with_unobtrusive.cs new file mode 100644 index 00000000000..706e01e8883 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DataBus/When_sending_databus_properties_with_unobtrusive.cs @@ -0,0 +1,84 @@ +namespace NServiceBus.AcceptanceTests.DataBus +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_databus_properties_with_unobtrusive : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_messages_with_largepayload_correctly() + { + var payloadToSend = new byte[PayloadSize]; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.Send(new MyMessageWithLargePayload + { + Payload = payloadToSend + }))) + .WithEndpoint() + .Done(c => c.ReceivedPayload != null) + .Run(); + + Assert.AreEqual(payloadToSend, context.ReceivedPayload, "The large payload should be marshalled correctly using the databus"); + } + + const int PayloadSize = 100; + + public class Context : ScenarioContext + { + public byte[] ReceivedPayload { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(builder => + { + builder.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyMessageWithLargePayload).FullName) + .DefiningDataBusPropertiesAs(t => t.Name.Contains("Payload")); + builder.UseDataBus().BasePath(@".\databus\sender"); + builder.UseSerialization(); + }) + .AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(builder => + { + builder.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyMessageWithLargePayload).FullName) + .DefiningDataBusPropertiesAs(t => t.Name.Contains("Payload")); + + builder.UseDataBus().BasePath(@".\databus\sender"); + builder.UseSerialization(); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessageWithLargePayload messageWithLargePayload, IMessageHandlerContext context) + { + Context.ReceivedPayload = messageWithLargePayload.Payload; + + return Task.FromResult(0); + } + } + } + + public class MyMessageWithLargePayload + { + public byte[] Payload { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DataBus/When_using_custom_IDataBus.cs b/src/NServiceBus.AcceptanceTests/DataBus/When_using_custom_IDataBus.cs index a38446fb388..7dac9a5960c 100644 --- a/src/NServiceBus.AcceptanceTests/DataBus/When_using_custom_IDataBus.cs +++ b/src/NServiceBus.AcceptanceTests/DataBus/When_using_custom_IDataBus.cs @@ -2,54 +2,30 @@ { using System; using System.IO; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NServiceBus.DataBus; using NUnit.Framework; public class When_using_custom_IDataBus : NServiceBusAcceptanceTest { - static byte[] PayloadToSend = new byte[1024 * 10]; - [Test] - public void Should_be_able_to_register_via_fluent() + public async Task Should_be_able_to_register_via_fluent() { - var context = new Context - { - TempPath = Path.GetTempFileName() - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.Send(new MyMessageWithLargePayload - { - Payload = new DataBusProperty(PayloadToSend) - }))) - .WithEndpoint() - .Done(c => c.ReceivedPayload != null) - .Run(); + var context = await Scenario.Define(c => { c.TempPath = Path.GetTempFileName(); }) + .WithEndpoint(b => b.When(session => session.Send(new MyMessageWithLargePayload + { + Payload = new DataBusProperty(PayloadToSend) + }))) + .WithEndpoint() + .Done(c => c.ReceivedPayload != null) + .Run(); Assert.AreEqual(PayloadToSend, context.ReceivedPayload, "The large payload should be marshalled correctly using the databus"); } - [Test] - public void Should_be_able_to_register_via_container() - { - var context = new Context - { - TempPath = Path.GetTempFileName() - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus=> bus.Send(new MyMessageWithLargePayload - { - Payload = new DataBusProperty(PayloadToSend) - }))) - .WithEndpoint() - .Done(c => c.ReceivedPayload != null) - .Run(); - - Assert.AreEqual(PayloadToSend, context.ReceivedPayload, "The large payload should be marshalled correctly using the databus"); - } + static byte[] PayloadToSend = new byte[1024*10]; public class Context : ScenarioContext { @@ -57,33 +33,6 @@ public class Context : ScenarioContext public byte[] ReceivedPayload { get; set; } } - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup(b => b.RegisterComponents(r => r.RegisterSingleton(new MyDataBus()))) - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup(b => b.RegisterComponents(r => r.RegisterSingleton(new MyDataBus()))); - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyMessageWithLargePayload messageWithLargePayload) - { - Context.ReceivedPayload = messageWithLargePayload.Payload.Value; - } - } - } - public class SenderViaFluent : EndpointConfigurationBuilder { public SenderViaFluent() @@ -104,9 +53,11 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessageWithLargePayload messageWithLargePayload) + public Task Handle(MyMessageWithLargePayload messageWithLargePayload, IMessageHandlerContext context) { Context.ReceivedPayload = messageWithLargePayload.Payload.Value; + + return Task.FromResult(0); } } } @@ -115,22 +66,24 @@ public class MyDataBus : IDataBus { public Context Context { get; set; } - public Stream Get(string key) + public Task Get(string key) { - return File.OpenRead(Context.TempPath); + var fileStream = new FileStream(Context.TempPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); + return Task.FromResult((Stream) fileStream); } - public string Put(Stream stream, TimeSpan timeToBeReceived) + public Task Put(Stream stream, TimeSpan timeToBeReceived) { using (var destination = File.OpenWrite(Context.TempPath)) { stream.CopyTo(destination); } - return "key"; + return Task.FromResult("key"); } - public void Start() + public Task Start() { + return Task.FromResult(0); } } @@ -140,6 +93,4 @@ public class MyMessageWithLargePayload : ICommand public DataBusProperty Payload { get; set; } } } - - -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_TimeoutManager_is_disabled.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_TimeoutManager_is_disabled.cs new file mode 100644 index 00000000000..cc34a01d32b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_TimeoutManager_is_disabled.cs @@ -0,0 +1,89 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_TimeoutManager_is_disabled : NServiceBusAcceptanceTest + { + [Test] + public Task Bus_Defer_should_throw() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + return session.Send(new MyMessage(), options); + })) + .Done(c => c.ExceptionThrown || c.SecondMessageReceived) + .Repeat(r => r.For()) + .Should(c => + { + Assert.AreEqual(true, c.ExceptionThrown); + Assert.AreEqual(false, c.SecondMessageReceived); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool ExceptionThrown { get; set; } + public bool SecondMessageReceived { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + // Explicitly disable TimeoutManager, although this should be default anyway + EndpointSetup(config => config.DisableFeature()); + } + + public class MyMessageHandler : IHandleMessages, IHandleMessages + { + public Context TestContext { get; set; } + + public async Task Handle(MyMessage message, IMessageHandlerContext context) + { + try + { + var opts = new SendOptions(); + opts.DelayDeliveryWith(TimeSpan.FromMilliseconds(1)); + opts.RouteToThisEndpoint(); + + await context.Send(new MyOtherMessage(), opts); + } + catch (Exception x) + { + Console.WriteLine(x.Message); + TestContext.ExceptionThrown = true; + } + } + + public Task Handle(MyOtherMessage message, IMessageHandlerContext context) + { + TestContext.SecondMessageReceived = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyMessage : IMessage + { + } + + [Serializable] + public class MyOtherMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails.cs new file mode 100644 index 00000000000..0541f12e2b3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails.cs @@ -0,0 +1,165 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Persistence; + using ScenarioDescriptors; + using Timeout.Core; + using Conventions = AcceptanceTesting.Customization.Conventions; + + public class When_timeout_dispatch_fails : NServiceBusAcceptanceTest + { + [Test] + public Task Should_retry_and_move_to_error() + { + return Scenario.Define() + .WithEndpoint(b => + b.DoNotFailOnErrorMessages() + .When((bus, c) => + { + var options = new SendOptions(); + options.DelayDeliveryWith(VeryLongTimeSpan); + options.RouteToThisEndpoint(); + options.SetMessageId(c.TestRunId.ToString()); + + return bus.Send(new MyMessage(), options); + })) + .Done(c => c.FailedTimeoutMovedToError) + .Repeat(r => r.For()) + .Should(c => + { + Assert.IsFalse(c.DelayedMessageDeliveredToHandler, "Message was unexpectedly delivered to the handler"); + Assert.IsTrue(c.FailedTimeoutMovedToError, "Message should have been moved to the error queue"); + }) + .Run(); + } + + static readonly TimeSpan VeryLongTimeSpan = TimeSpan.FromMinutes(10); + public class Context : ScenarioContext + { + public bool FailedTimeoutMovedToError { get; set; } + public bool DelayedMessageDeliveredToHandler { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => + { + config.EnableFeature(); + config.UsePersistence(); + config.SendFailedMessagesTo(Conventions.EndpointNamingConvention(typeof(Endpoint))); + config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); + config.Pipeline.Register(); + }); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + TestContext.DelayedMessageDeliveredToHandler = true; + return Task.FromResult(0); + } + } + + class FakeTimeoutPersistence : PersistenceDefinition + { + public FakeTimeoutPersistence() + { + Supports(s => { }); + } + } + + class FakeTimeoutStorage : IQueryTimeouts, IPersistTimeouts + { + public Context TestContext { get; set; } + + public Task Add(TimeoutData timeout, ContextBag context) + { + if (TestContext.TestRunId.ToString() == timeout.Headers[Headers.MessageId]) + { + timeout.Id = TestContext.TestRunId.ToString(); + timeout.Time = DateTime.UtcNow; + + timeoutData = timeout; + } + + return Task.FromResult(0); + } + + public Task TryRemove(string timeoutId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task Peek(string timeoutId, ContextBag context) + { + if (timeoutId != TestContext.TestRunId.ToString()) + { + throw new ArgumentException("The timeoutId is different from one registered in the test context", "timeoutId"); + } + + throw new Exception("Ooops. Timeout storage went bust"); + } + + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task GetNextChunk(DateTime startSlice) + { + var timeouts = timeoutData != null + ? new[] + { + new TimeoutsChunk.Timeout(timeoutData.Id, timeoutData.Time) + } + : new TimeoutsChunk.Timeout[0]; + + return Task.FromResult(new TimeoutsChunk(timeouts, DateTime.UtcNow + TimeSpan.FromSeconds(10))); + } + + TimeoutData timeoutData; + } + + class BehaviorThatLogsControlMessageDelivery : IBehavior + { + public Context TestContext { get; set; } + + public Task Invoke(ITransportReceiveContext context, Func next) + { + if (context.Message.Headers.ContainsKey(Headers.ControlMessageHeader) && + context.Message.Headers["Timeout.Id"] == TestContext.TestRunId.ToString()) + { + TestContext.FailedTimeoutMovedToError = true; + return Task.FromResult(0); + } + + return next(context); + } + + public class Registration : RegisterStep + { + public Registration() : base("BehaviorThatLogsControlMessageDelivery", typeof(BehaviorThatLogsControlMessageDelivery), "BehaviorThatLogsControlMessageDelivery") + { + } + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails_on_timeout_data_removal.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails_on_timeout_data_removal.cs new file mode 100644 index 00000000000..5118b49b7fd --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_dispatch_fails_on_timeout_data_removal.cs @@ -0,0 +1,168 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Persistence; + using ScenarioDescriptors; + using Timeout.Core; + using Conventions = AcceptanceTesting.Customization.Conventions; + + public class When_timeout_dispatch_fails_on_timeout_data_removal : NServiceBusAcceptanceTest + { + [Test] + public Task Should_move_control_message_to_errors_and_not_dispatch_original_message_to_handler() + { + return Scenario.Define() + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((bus, c) => + { + var options = new SendOptions(); + options.DelayDeliveryWith(TimeSpan.FromMinutes(1)); + options.RouteToThisEndpoint(); + options.SetMessageId(c.TestRunId.ToString()); + + return bus.Send(new MyMessage + { + Id = c.TestRunId + }, options); + })) + .Done(c => c.FailedTimeoutMovedToError) + .Repeat(r => r.For()) + .Should(c => Assert.IsFalse(c.DelayedMessageDeliveredToHandler, "Message was unexpectedly delivered to the handler")) + .Run(); + } + + public class Context : ScenarioContext + { + public bool FailedTimeoutMovedToError { get; set; } + public bool DelayedMessageDeliveredToHandler { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => + { + config.EnableFeature(); + config.UsePersistence(); + config.SendFailedMessagesTo(Conventions.EndpointNamingConvention(typeof(Endpoint))); + config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); + config.Pipeline.Register(); + config.LimitMessageProcessingConcurrencyTo(1); + }); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (message.Id == TestContext.TestRunId) + { + TestContext.DelayedMessageDeliveredToHandler = true; + } + + return Task.FromResult(0); + } + } + + class FakeTimeoutPersistence : PersistenceDefinition + { + public FakeTimeoutPersistence() + { + Supports(s => { }); + } + } + + class FakeTimeoutStorage : IQueryTimeouts, IPersistTimeouts + { + public Context TestContext { get; set; } + + public Task Add(TimeoutData timeout, ContextBag context) + { + if (TestContext.TestRunId.ToString() == timeout.Headers[Headers.MessageId]) + { + timeout.Id = TestContext.TestRunId.ToString(); + timeout.Time = DateTime.UtcNow; + + timeoutData = timeout; + } + + return Task.FromResult(0); + } + + public Task TryRemove(string timeoutId, ContextBag context) + { + throw new Exception("Simulated exception on removing timeout data."); + } + + public Task Peek(string timeoutId, ContextBag context) + { + if (timeoutId == TestContext.TestRunId.ToString() && timeoutData != null) + { + return Task.FromResult(timeoutData); + } + + return Task.FromResult((TimeoutData) null); + } + + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task GetNextChunk(DateTime startSlice) + { + var timeouts = timeoutData != null + ? new[] + { + new TimeoutsChunk.Timeout(timeoutData.Id, timeoutData.Time) + } + : new TimeoutsChunk.Timeout[0]; + + return Task.FromResult(new TimeoutsChunk(timeouts, DateTime.UtcNow + TimeSpan.FromSeconds(10))); + } + + TimeoutData timeoutData; + } + + class BehaviorThatLogsControlMessageDelivery : IBehavior + { + public Context TestContext { get; set; } + + public Task Invoke(ITransportReceiveContext context, Func next) + { + if (context.Message.Headers.ContainsKey(Headers.ControlMessageHeader) && + context.Message.Headers["Timeout.Id"] == TestContext.TestRunId.ToString()) + { + TestContext.FailedTimeoutMovedToError = true; + return Task.FromResult(0); + } + + return next(context); + } + + public class Registration : RegisterStep + { + public Registration() : base("BehaviorThatLogsControlMessageDelivery", typeof(BehaviorThatLogsControlMessageDelivery), "BehaviorThatLogsControlMessageDelivery") + { + } + } + } + } + + public class MyMessage : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_storage_fails.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_storage_fails.cs new file mode 100644 index 00000000000..192b42e388c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_timeout_storage_fails.cs @@ -0,0 +1,130 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NUnit.Framework; + using Persistence; + using ScenarioDescriptors; + using Timeout.Core; + + public class When_timeout_storage_fails : NServiceBusAcceptanceTest + { + [Test] + public Task Should_retry_and_move_to_error() + { + return Scenario.Define() + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((bus, c) => + { + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromDays(30)); + + options.RouteToThisEndpoint(); + + return bus.Send(new MyMessage(), options); + })) + .WithEndpoint() + .Done(c => c.FailedTimeoutMovedToError) + .Repeat(r => r.For()) + .Should(c => Assert.AreEqual(5, c.NumTimesStorageCalled)) + .Run(); + } + + const string ErrorQueueForTimeoutErrors = "timeout_store_errors"; + + public class Context : ScenarioContext + { + public bool FailedTimeoutMovedToError { get; set; } + public int NumTimesStorageCalled { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => + { + config.EnableFeature(); + config.UsePersistence(); + config.SendFailedMessagesTo(ErrorQueueForTimeoutErrors); + config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + public class FakeTimeoutPersistence : PersistenceDefinition + { + public FakeTimeoutPersistence() + { + Supports(s => { }); + } + } + + public class FakeTimeoutStorage : IQueryTimeouts, IPersistTimeouts + { + public FakeTimeoutStorage(Context context) + { + testContext = context; + } + + public Task Add(TimeoutData timeout, ContextBag context) + { + testContext.NumTimesStorageCalled++; + + throw new Exception("Simulated exception on storing timeout."); + } + + public Task TryRemove(string timeoutId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task Peek(string timeoutId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task GetNextChunk(DateTime startSlice) + { + return Task.FromResult(new TimeoutsChunk(new TimeoutsChunk.Timeout[0], DateTime.UtcNow + TimeSpan.FromSeconds(10))); + } + + Context testContext; + } + } + + public class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup() + .CustomEndpointName(ErrorQueueForTimeoutErrors); + } + + class Handler : IHandleMessages + { + public Context MyContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + MyContext.FailedTimeoutMovedToError = true; + return Task.FromResult(0); + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_using_external_timeout_manager.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_using_external_timeout_manager.cs new file mode 100644 index 00000000000..03132ea9b05 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/TimeoutManager/When_using_external_timeout_manager.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using Features; + using NServiceBus.Config; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_using_external_timeout_manager : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_delay_delivery() + { + await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromMilliseconds(2000)); + options.RouteToThisEndpoint(); + + return session.Send(new MyMessage(), options); + })) + .Done(c => c.WasCalled) + .Repeat(r => r.For()) + .Should(c => { Assert.IsTrue(c.TimeoutManagerHeaderDetected); }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool TimeoutManagerHeaderDetected { get; set; } + public bool WasCalled { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + var address = Conventions.EndpointNamingConvention(typeof(EndpointWithTimeoutManager)) + ".Timeouts"; + + EndpointSetup(config => config.DisableFeature()) + .WithConfig(c => { c.TimeoutManagerAddress = address; }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.TimeoutManagerHeaderDetected = context.MessageHeaders.ContainsKey("NServiceBus.RelatedToTimeoutId"); + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + public class EndpointWithTimeoutManager : EndpointConfigurationBuilder + { + public EndpointWithTimeoutManager() + { + EndpointSetup(config => config.EnableFeature()); + } + } + + public class MyMessage : IMessage + { + } + } +} diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_Deferring_a_message.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_Deferring_a_message.cs new file mode 100644 index 00000000000..c073df109f9 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_Deferring_a_message.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_deferring_a_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_delay_delivery() + { + var delay = TimeSpan.FromMilliseconds(1); + + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.DelayDeliveryWith(delay); + options.RouteToThisEndpoint(); + + c.SentAt = DateTime.UtcNow; + + return session.Send(new MyMessage(), options); + })) + .Done(c => c.WasCalled) + .Run(); + + Assert.GreaterOrEqual(context.ReceivedAt - context.SentAt, delay); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public DateTime SentAt { get; set; } + public DateTime ReceivedAt { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => config.EnableFeature()); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.ReceivedAt = DateTime.UtcNow; + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_deferring_a_message_to_the_past.cs b/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_deferring_a_message_to_the_past.cs new file mode 100644 index 00000000000..25ad3f6e9bc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/DelayedDelivery/When_deferring_a_message_to_the_past.cs @@ -0,0 +1,60 @@ +namespace NServiceBus.AcceptanceTests.DelayedDelivery +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_deferring_a_message_to_the_past : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_deliver_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((bus, c) => + { + var options = new SendOptions(); + + options.DoNotDeliverBefore(DateTime.Now.AddHours(-1)); + options.RouteToThisEndpoint(); + + return bus.Send(new MyMessage(), options); + })) + .Done(c => c.MessageReceived) + .Run(); + + Assert.IsTrue(context.MessageReceived); + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => config.EnableFeature()); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.MessageReceived = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/DeterministicGuid.cs b/src/NServiceBus.AcceptanceTests/DeterministicGuid.cs index e062b2b9a17..97248bb16b2 100644 --- a/src/NServiceBus.AcceptanceTests/DeterministicGuid.cs +++ b/src/NServiceBus.AcceptanceTests/DeterministicGuid.cs @@ -11,11 +11,11 @@ public static Guid Create(params object[] data) // use MD5 hash to get a 16-byte hash of the string using (var provider = new MD5CryptoServiceProvider()) { - var inputBytes = Encoding.Default.GetBytes(String.Concat(data)); + var inputBytes = Encoding.Default.GetBytes(string.Concat(data)); var hashBytes = provider.ComputeHash(inputBytes); // generate a guid from the hash: return new Guid(hashBytes); } - } + } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_config.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_config.cs index 3d85befa2c0..aff7f43dcac 100644 --- a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_config.cs +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_config.cs @@ -2,8 +2,9 @@ { using System; using System.Collections.Generic; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NServiceBus.Config; using NServiceBus.Config.ConfigurationSource; using NUnit.Framework; @@ -11,33 +12,40 @@ public class When_using_Rijndael_with_config : NServiceBusAcceptanceTest { [Test] - public void Should_receive_decrypted_message() + public async Task Should_receive_decrypted_message() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageWithSecretData + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageWithSecretData + { + Secret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty + { + Secret = "My sub secret" + }, + CreditCards = new List + { + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(1), + Number = "312312312312312" + }, + new CreditCardDetails { - Secret = "betcha can't guess my secret", - SubProperty = new MySecretSubProperty {Secret = "My sub secret"}, - CreditCards = new List - { - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(1), - Number = "312312312312312" - }, - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(2), - Number = "543645546546456" - } - } - }))) - .Done(c => c.GotTheMessage) - .Run(); + ValidTo = DateTime.UtcNow.AddYears(2), + Number = "543645546546456" + } + } + }))) + .Done(c => c.GotTheMessage) + .Run(); Assert.AreEqual("betcha can't guess my secret", context.Secret); Assert.AreEqual("My sub secret", context.SubPropertySecret); - CollectionAssert.AreEquivalent(new List { "312312312312312", "543645546546456" }, context.CreditCards); + CollectionAssert.AreEquivalent(new List + { + "312312312312312", + "543645546546456" + }, context.CreditCards); } public class Context : ScenarioContext @@ -62,7 +70,7 @@ public class Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(MessageWithSecretData message) + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { Context.Secret = message.Secret.Value; @@ -70,11 +78,13 @@ public void Handle(MessageWithSecretData message) Context.CreditCards = new List { - message.CreditCards[0].Number.Value, + message.CreditCards[0].Number.Value, message.CreditCards[1].Number.Value }; Context.GotTheMessage = true; + + return Task.FromResult(0); } } } @@ -100,7 +110,7 @@ public class MySecretSubProperty public WireEncryptedString Secret { get; set; } } - public class ConfigureEncryption: IProvideConfiguration + public class ConfigureEncryption : IProvideConfiguration { public RijndaelEncryptionServiceConfig GetConfiguration() { diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_custom.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_custom.cs index d25d93eea55..27c9c20b07e 100644 --- a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_custom.cs +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_custom.cs @@ -3,40 +3,48 @@ using System; using System.Collections.Generic; using System.Text; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_using_Rijndael_with_custom : NServiceBusAcceptanceTest { [Test] - public void Should_receive_decrypted_message() + public async Task Should_receive_decrypted_message() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageWithSecretData + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageWithSecretData + { + Secret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty + { + Secret = "My sub secret" + }, + CreditCards = new List + { + new CreditCardDetails { - Secret = "betcha can't guess my secret", - SubProperty = new MySecretSubProperty { Secret = "My sub secret" }, - CreditCards = new List - { - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(1), - Number = "312312312312312" - }, - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(2), - Number = "543645546546456" - } - } - }))) - .Done(c => c.GetTheMessage) - .Run(); + ValidTo = DateTime.UtcNow.AddYears(1), + Number = "312312312312312" + }, + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(2), + Number = "543645546546456" + } + } + }))) + .Done(c => c.GetTheMessage) + .Run(); Assert.AreEqual("betcha can't guess my secret", context.Secret); Assert.AreEqual("My sub secret", context.SubPropertySecret); - CollectionAssert.AreEquivalent(new List { "312312312312312", "543645546546456" }, context.CreditCards); + CollectionAssert.AreEquivalent(new List + { + "312312312312312", + "543645546546456" + }, context.CreditCards); } public class Context : ScenarioContext @@ -56,7 +64,7 @@ public Endpoint() { var keys = new Dictionary { - {"1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")} + {"1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")} }; EndpointSetup(builder => builder.RijndaelEncryptionService("1st", keys)); @@ -66,7 +74,7 @@ public class Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(MessageWithSecretData message) + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { Context.Secret = message.Secret.Value; @@ -74,11 +82,13 @@ public void Handle(MessageWithSecretData message) Context.CreditCards = new List { - message.CreditCards[0].Number.Value, + message.CreditCards[0].Number.Value, message.CreditCards[1].Number.Value }; Context.GetTheMessage = true; + + return Task.FromResult(0); } } } @@ -103,6 +113,5 @@ public class MySecretSubProperty { public WireEncryptedString Secret { get; set; } } - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_multikey.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_multikey.cs index 1e834f557ba..f8731ae0ad7 100644 --- a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_multikey.cs +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_multikey.cs @@ -3,41 +3,33 @@ using System; using System.Collections.Generic; using System.Text; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; using ScenarioDescriptors; public class When_using_Rijndael_with_multikey : NServiceBusAcceptanceTest { [Test] - public void Should_receive_decrypted_message() + public Task Should_receive_decrypted_message() { - Scenario.Define() - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Send(new MessageWithSecretData - { - Secret = "betcha can't guess my secret", - }); - bus.Send(new RegularMessage()); - })) - .WithEndpoint() - .Done(c => c.Done) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.AreEqual("betcha can't guess my secret", c.Secret); - Assert.IsFalse(c.HasKeyOnRegularMessage.Value, "Key identifier header present in message without encrypted properties."); - }) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When((session, context) => session.Send(new MessageWithSecretData + { + Secret = "betcha can't guess my secret" + }))) + .WithEndpoint() + .Done(c => c.Done) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.AreEqual("betcha can't guess my secret", c.Secret)) + .Run(); } public class Context : ScenarioContext { public bool Done { get; set; } public string Secret { get; set; } - public bool? HasKeyOnRegularMessage { get; set; } } public class Sender : EndpointConfigurationBuilder @@ -45,9 +37,7 @@ public class Sender : EndpointConfigurationBuilder public Sender() { EndpointSetup(builder => builder.RijndaelEncryptionService("1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"))) - .AddMapping(typeof(Receiver)) - .AddMapping(typeof(Receiver)) - ; + .AddMapping(typeof(Receiver)); } } @@ -58,29 +48,27 @@ public Receiver() var key = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); var keys = new Dictionary { - {"2nd", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6") }, - {"1st", key } + {"2nd", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")}, + {"1st", key} }; - var expiredKeys = new[] { key }; + var expiredKeys = new[] + { + key + }; EndpointSetup(builder => builder.RijndaelEncryptionService("2nd", keys, expiredKeys)); } - public class Handler : IHandleMessages, IHandleMessages + public class Handler : IHandleMessages { public Context Context { get; set; } - public IBus Bus { get; set; } - public void Handle(MessageWithSecretData message) + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { Context.Secret = message.Secret.Value; Context.Done = true; - } - public void Handle(RegularMessage message) - { - var hasKey = null != Bus.GetMessageHeader(message, Headers.RijndaelKeyIdentifier); - Context.HasKeyOnRegularMessage = hasKey; + return Task.FromResult(0); } } } @@ -90,10 +78,5 @@ public class MessageWithSecretData : IMessage { public WireEncryptedString Secret { get; set; } } - - [Serializable] - public class RegularMessage : IMessage - { - } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_unobtrusive_mode.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_unobtrusive_mode.cs new file mode 100644 index 00000000000..022481ecf5e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_with_unobtrusive_mode.cs @@ -0,0 +1,139 @@ +namespace NServiceBus.AcceptanceTests.Encryption +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_using_Rijndael_with_unobtrusive_mode : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_decrypted_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.Send(new MessageWithSecretData + { + EncryptedSecret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty + { + EncryptedSecret = "My sub secret" + }, + CreditCards = new List + { + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(1), + EncryptedNumber = "312312312312312" + }, + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(2), + EncryptedNumber = "543645546546456" + } + } + }))) + .WithEndpoint() + .Done(c => c.GetTheMessage || c.FailedMessages.Any()) + .Run(); + + Assert.AreEqual("betcha can't guess my secret", context.Secret); + Assert.AreEqual("My sub secret", context.SubPropertySecret); + CollectionAssert.AreEquivalent(new List + { + "312312312312312", + "543645546546456" + }, context.CreditCards); + } + + static Dictionary Keys = new Dictionary + { + {"1st", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")} + }; + + public class Context : ScenarioContext + { + public bool GetTheMessage { get; set; } + + public string Secret { get; set; } + + public string SubPropertySecret { get; set; } + + public List CreditCards { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName) + .DefiningEncryptedPropertiesAs(t => t.Name.StartsWith("Encrypted")); + + c.RijndaelEncryptionService("1st", Keys); + }).AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MessageWithSecretData).FullName) + .DefiningEncryptedPropertiesAs(t => t.Name.StartsWith("Encrypted")); + + c.RijndaelEncryptionService("1st", Keys); + }); + } + + public class Handler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) + { + Context.Secret = message.EncryptedSecret; + + Context.SubPropertySecret = message.SubProperty.EncryptedSecret; + + Context.CreditCards = new List + { + message.CreditCards[0].EncryptedNumber, + message.CreditCards[1].EncryptedNumber + }; + + Context.GetTheMessage = true; + + return Task.FromResult(0); + } + } + } + + public class MessageWithSecretData + { + public string EncryptedSecret { get; set; } + public MySecretSubProperty SubProperty { get; set; } + public List CreditCards { get; set; } + } + + public class CreditCardDetails + { + public DateTime ValidTo { get; set; } + public string EncryptedNumber { get; set; } + } + + public class MySecretSubProperty + { + public string EncryptedSecret { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_without_incoming_key_identifier.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_without_incoming_key_identifier.cs index 695107e5857..a3318537f13 100644 --- a/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_without_incoming_key_identifier.cs +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_Rijndael_without_incoming_key_identifier.cs @@ -3,27 +3,28 @@ using System; using System.Collections.Generic; using System.Text; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; - using NServiceBus.MessageMutator; + using EndpointTemplates; + using MessageMutator; using NUnit.Framework; using ScenarioDescriptors; public class When_using_Rijndael_without_incoming_key_identifier : NServiceBusAcceptanceTest { [Test] - public void Should_process_decrypted_message_without_key_identifier() + public Task Should_process_decrypted_message_without_key_identifier() { - Scenario.Define() - .WithEndpoint(b => b.Given((bus, context) => bus.Send(new MessageWithSecretData - { - Secret = "betcha can't guess my secret", - }))) - .WithEndpoint() - .Done(c => c.Done) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.AreEqual("betcha can't guess my secret", c.Secret)) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When((bus, context) => bus.Send(new MessageWithSecretData + { + Secret = "betcha can't guess my secret" + }))) + .WithEndpoint() + .Done(c => c.Done) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.AreEqual("betcha can't guess my secret", c.Secret)) + .Run(); } public class Context : ScenarioContext @@ -47,22 +48,25 @@ public Receiver() { var keys = new Dictionary { - {"new", Encoding.ASCII.GetBytes("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") }, + {"new", Encoding.ASCII.GetBytes("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")} }; - var expiredKeys = new[] { Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") }; + var expiredKeys = new[] + { + Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + }; EndpointSetup(builder => builder.RijndaelEncryptionService("new", keys, expiredKeys)); - } public class Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(MessageWithSecretData message) + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { Context.Secret = message.Secret.Value; Context.Done = true; + return Task.FromResult(0); } } } @@ -73,18 +77,18 @@ public class MessageWithSecretData : IMessage public WireEncryptedString Secret { get; set; } } - class RemoveKeyIdentifierHeaderMutator : IMutateIncomingTransportMessages, INeedInitialization { - public void MutateIncoming(TransportMessage transportMessage) + public Task MutateIncoming(MutateIncomingTransportMessageContext context) { - transportMessage.Headers.Remove(Headers.RijndaelKeyIdentifier); + context.Headers.Remove(Headers.RijndaelKeyIdentifier); + return Task.FromResult(0); } - public void Customize(BusConfiguration configuration) + public void Customize(EndpointConfiguration configuration) { configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Encryption/When_using_encryption_with_custom_service.cs b/src/NServiceBus.AcceptanceTests/Encryption/When_using_encryption_with_custom_service.cs index 65a9dc0e59f..c5e777048a3 100644 --- a/src/NServiceBus.AcceptanceTests/Encryption/When_using_encryption_with_custom_service.cs +++ b/src/NServiceBus.AcceptanceTests/Encryption/When_using_encryption_with_custom_service.cs @@ -3,41 +3,49 @@ using System; using System.Collections.Generic; using System.Linq; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; - using NServiceBus.Encryption; + using EndpointTemplates; + using NServiceBus.Pipeline; using NUnit.Framework; public class When_using_encryption_with_custom_service : NServiceBusAcceptanceTest { [Test] - public void Should_receive_decrypted_message() + public async Task Should_receive_decrypted_message() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageWithSecretData + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageWithSecretData + { + Secret = "betcha can't guess my secret", + SubProperty = new MySecretSubProperty + { + Secret = "My sub secret" + }, + CreditCards = new List + { + new CreditCardDetails + { + ValidTo = DateTime.UtcNow.AddYears(1), + Number = "312312312312312" + }, + new CreditCardDetails { - Secret = "betcha can't guess my secret", - SubProperty = new MySecretSubProperty {Secret = "My sub secret"}, - CreditCards = new List - { - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(1), - Number = "312312312312312" - }, - new CreditCardDetails - { - ValidTo = DateTime.UtcNow.AddYears(2), - Number = "543645546546456" - } - } - }))) - .Done(c => c.GotTheMessage) - .Run(); + ValidTo = DateTime.UtcNow.AddYears(2), + Number = "543645546546456" + } + } + }))) + .Done(c => c.GotTheMessage) + .Run(); Assert.AreEqual("betcha can't guess my secret", context.Secret); Assert.AreEqual("My sub secret", context.SubPropertySecret); - CollectionAssert.AreEquivalent(new List { "312312312312312", "543645546546456" }, context.CreditCards); + CollectionAssert.AreEquivalent(new List + { + "312312312312312", + "543645546546456" + }, context.CreditCards); } public class Context : ScenarioContext @@ -55,14 +63,14 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(builder => builder.RegisterEncryptionService(_ => new MyEncryptionService())); + EndpointSetup(builder => builder.RegisterEncryptionService(() => new MyEncryptionService())); } public class Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(MessageWithSecretData message) + public Task Handle(MessageWithSecretData message, IMessageHandlerContext context) { Context.Secret = message.Secret.Value; @@ -70,11 +78,13 @@ public void Handle(MessageWithSecretData message) Context.CreditCards = new List { - message.CreditCards[0].Number.Value, + message.CreditCards[0].Number.Value, message.CreditCards[1].Number.Value }; Context.GotTheMessage = true; + + return Task.FromResult(0); } } } @@ -100,9 +110,9 @@ public class MySecretSubProperty public WireEncryptedString Secret { get; set; } } - public class MyEncryptionService: IEncryptionService + public class MyEncryptionService : IEncryptionService { - public EncryptedValue Encrypt(string value) + public EncryptedValue Encrypt(string value, IOutgoingLogicalMessageContext context) { return new EncryptedValue { @@ -110,7 +120,7 @@ public EncryptedValue Encrypt(string value) }; } - public string Decrypt(EncryptedValue encryptedValue) + public string Decrypt(EncryptedValue encryptedValue, IIncomingLogicalMessageContext context) { return new string(encryptedValue.EncryptedBase64Value.Reverse().ToArray()); } diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/ConfigureExtensions.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/ConfigureExtensions.cs index 6fde4375b58..0678ee80c3b 100644 --- a/src/NServiceBus.AcceptanceTests/EndpointTemplates/ConfigureExtensions.cs +++ b/src/NServiceBus.AcceptanceTests/EndpointTemplates/ConfigureExtensions.cs @@ -1,72 +1,53 @@ namespace NServiceBus.AcceptanceTests.EndpointTemplates { using System; - using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting.Support; + using ObjectBuilder; using ScenarioDescriptors; public static class ConfigureExtensions { - public static string GetOrNull(this IDictionary dictionary, string key) + public static Task DefineTransport(this EndpointConfiguration config, RunSettings settings, string endpointName) { - if (!dictionary.ContainsKey(key)) + Type transportType; + if (!settings.TryGet("Transport", out transportType)) { - return null; + settings.Merge(Transports.Default.Settings); } - return dictionary[key]; + return ConfigureTestExecution(TestDependencyType.Transport, config, settings, endpointName); } - public static void DefineTransport(this BusConfiguration builder, IDictionary settings, Type endpointBuilderType) + public static Task DefinePersistence(this EndpointConfiguration config, RunSettings settings, string endpointName) { - if (!settings.ContainsKey("Transport")) + Type persistenceType; + if (!settings.TryGet("Persistence", out persistenceType)) { - settings = Transports.Default.Settings; + settings.Merge(Persistence.Default.Settings); } - const string typeName = "ConfigureTransport"; - - var transportType = Type.GetType(settings["Transport"]); - var transportTypeName = "Configure" + transportType.Name; - - var configurerType = endpointBuilderType.GetNestedType(typeName) ?? - Type.GetType(transportTypeName, false); - - if (configurerType != null) - { - var configurer = Activator.CreateInstance(configurerType); - - dynamic dc = configurer; - - dc.Configure(builder); - return; - } - - builder.UseTransport(transportType).ConnectionString(settings["Transport.ConnectionString"]); + return ConfigureTestExecution(TestDependencyType.Persistence, config, settings, endpointName); } - public static void DefineTransactions(this BusConfiguration config, IDictionary settings) + public static void DefineBuilder(this EndpointConfiguration config, RunSettings settings) { - if (settings.ContainsKey("Transactions.Disable")) + Type builderType; + if (!settings.TryGet("Builder", out builderType)) { - config.Transactions().Disable(); - } - if (settings.ContainsKey("Transactions.SuppressDistributedTransactions")) - { - config.Transactions().DisableDistributedTransactions(); - } - } + var builderDescriptor = Builders.Default; - public static void DefinePersistence(this BusConfiguration config, IDictionary settings) - { - if (!settings.ContainsKey("Persistence")) - { - settings = Persistence.Default.Settings; - } + if (builderDescriptor == null) + { + return; //go with the default builder + } - var persistenceType = Type.GetType(settings["Persistence"]); + settings.Merge(builderDescriptor.Settings); + } + builderType = settings.Get("Builder"); - var typeName = "Configure" + persistenceType.Name; + var typeName = "Configure" + builderType.Name; var configurerType = Type.GetType(typeName, false); @@ -77,43 +58,64 @@ public static void DefinePersistence(this BusConfiguration config, IDictionary settings) + public static void RegisterComponentsAndInheritanceHierarchy(this EndpointConfiguration builder, RunDescriptor runDescriptor) { - if (!settings.ContainsKey("Builder")) - { - var builderDescriptor = Builders.Default; + builder.RegisterComponents(r => { RegisterInheritanceHierarchyOfContextOnContainer(runDescriptor, r); }); + } - if (builderDescriptor == null) - { - return; //go with the default builder - } + static async Task ConfigureTestExecution(TestDependencyType type, EndpointConfiguration config, RunSettings settings, string endpointName) + { + var dependencyTypeString = type.ToString(); - settings = builderDescriptor.Settings; - } + var dependencyType = settings.Get(dependencyTypeString); - var builderType = Type.GetType(settings["Builder"]); + var typeName = "ConfigureEndpoint" + dependencyType.Name; + var configurerType = Type.GetType(typeName, false); - var typeName = "Configure" + builderType.Name; + if (configurerType == null) + { + throw new InvalidOperationException($"Acceptance Test project must include a non-namespaced class named '{typeName}' implementing {typeof(IConfigureEndpointTestExecution).Name}. See {typeof(ConfigureEndpointMsmqTransport).FullName} for an example."); + } - var configurerType = Type.GetType(typeName, false); + var configurer = Activator.CreateInstance(configurerType) as IConfigureEndpointTestExecution; - if (configurerType != null) + if (configurer == null) { - var configurer = Activator.CreateInstance(configurerType); + throw new InvalidOperationException($"{typeName} does not implement {typeof(IConfigureEndpointTestExecution).Name}."); + } - dynamic dc = configurer; + await configurer.Configure(endpointName, config, settings).ConfigureAwait(false); - dc.Configure(config); + ActiveTestExecutionConfigurer cleaners; + var cleanerKey = "ConfigureTestExecution." + endpointName; + if (!settings.TryGet(cleanerKey, out cleaners)) + { + cleaners = new ActiveTestExecutionConfigurer(); + settings.Set(cleanerKey, cleaners); } + cleaners.Add(configurer); + } - config.UseContainer(builderType); + static void RegisterInheritanceHierarchyOfContextOnContainer(RunDescriptor runDescriptor, IConfigureComponents r) + { + var type = runDescriptor.ScenarioContext.GetType(); + while (type != typeof(object)) + { + r.RegisterSingleton(type, runDescriptor.ScenarioContext); + type = type.BaseType; + } + } + + enum TestDependencyType + { + Transport, + Persistence } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/ContextAppender.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/ContextAppender.cs deleted file mode 100644 index 10662951a6c..00000000000 --- a/src/NServiceBus.AcceptanceTests/EndpointTemplates/ContextAppender.cs +++ /dev/null @@ -1,129 +0,0 @@ -namespace NServiceBus.AcceptanceTests.EndpointTemplates -{ - using System; - using System.Diagnostics; - using AcceptanceTesting; - using Logging; - - public class ContextAppender : ILoggerFactory, ILog - { - public ContextAppender(ScenarioContext context, string endpointName) - { - this.context = context; - this.endpointName = endpointName; - } - - void Append(Exception exception) - { - lock (context) - { - context.Exceptions += exception + "/n/r"; - } - } - - ScenarioContext context; - readonly string endpointName; - - public ILog GetLogger(Type type) - { - return this; - } - - public ILog GetLogger(string name) - { - return this; - } - - public bool IsDebugEnabled { get{return true;}} - public bool IsInfoEnabled { get { return true; } } - public bool IsWarnEnabled { get { return true; } } - public bool IsErrorEnabled { get { return true; } } - public bool IsFatalEnabled { get { return true; } } - - public void Debug(string message) - { - Trace.WriteLine(message); - } - - public void Debug(string message, Exception exception) - { - Trace.WriteLine(string.Format("{0} {1}", message, exception)); - Append(exception); - } - - public void DebugFormat(string format, params object[] args) - { - Trace.WriteLine(string.Format(format,args)); - } - - public void Info(string message) - { - Trace.WriteLine(message); - } - - public void Info(string message, Exception exception) - { - Trace.WriteLine(string.Format("{0} {1}", message, exception)); - Append(exception); - } - - public void InfoFormat(string format, params object[] args) - { - Trace.WriteLine(string.Format(format, args)); - } - - public void Warn(string message) - { - Trace.WriteLine(message); - } - - public void Warn(string message, Exception exception) - { - Trace.WriteLine(string.Format("{0} {1}", message, exception)); - Append(exception); - } - - public void WarnFormat(string format, params object[] args) - { - Trace.WriteLine(string.Format(format, args)); - } - - public void Error(string message) - { - Trace.WriteLine(message); - - context.RecordEndpointLog(endpointName,"error", message); - } - - public void Error(string message, Exception exception) - { - var fullMessage = string.Format("{0} {1}", message, exception); - Trace.WriteLine(fullMessage); - Append(exception); - context.RecordEndpointLog(endpointName, "error", fullMessage); - } - - public void ErrorFormat(string format, params object[] args) - { - var fullMessage = string.Format(format, args); - Trace.WriteLine(fullMessage); - context.RecordEndpointLog(endpointName, "error", fullMessage); - } - - public void Fatal(string message) - { - Trace.WriteLine(message); - } - - public void Fatal(string message, Exception exception) - { - Trace.WriteLine(string.Format("{0} {1}", message, exception)); - Append(exception); - } - - public void FatalFormat(string format, params object[] args) - { - Trace.WriteLine(string.Format(format, args)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultPublisher.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultPublisher.cs index 64ba96c4350..5b40c81f593 100644 --- a/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultPublisher.cs +++ b/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultPublisher.cs @@ -1,55 +1,15 @@ namespace NServiceBus.AcceptanceTests.EndpointTemplates { using System; - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; + using System.Threading.Tasks; + using AcceptanceTesting.Support; using NServiceBus.Config.ConfigurationSource; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; public class DefaultPublisher : IEndpointSetupTemplate { - public BusConfiguration GetConfiguration(RunDescriptor runDescriptor, EndpointConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) + public Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) { - return new DefaultServer(new List { typeof(SubscriptionTracer), typeof(SubscriptionTracer.Registration) }).GetConfiguration(runDescriptor, endpointConfiguration, configSource, b => - { - b.Pipeline.Register(); - configurationBuilderCustomization(b); - }); - } - - class SubscriptionTracer : IBehavior - { - public ScenarioContext Context { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - next(); - - List
subscribers; - - if (context.TryGet("SubscribersForEvent", out subscribers)) - { - Context.AddTrace(string.Format("Subscribers for {0} : {1}", context.OutgoingLogicalMessage.MessageType.Name, string.Join(";", subscribers))); - } - - bool nosubscribers; - - if (context.TryGet("NoSubscribersFoundForMessage", out nosubscribers) && nosubscribers) - { - Context.AddTrace(string.Format("No Subscribers found for message {0}", context.OutgoingLogicalMessage.MessageType.Name)); - } - } - - public class Registration : RegisterStep - { - public Registration() - : base("SubscriptionTracer", typeof(SubscriptionTracer), "Traces the list of found subscribers") - { - InsertBefore(WellKnownStep.DispatchMessageToTransport); - } - } + return new DefaultServer().GetConfiguration(runDescriptor, endpointConfiguration, configSource, configurationBuilderCustomization); } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultServer.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultServer.cs index e02e1e4edd3..83c7c7c5e99 100644 --- a/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultServer.cs +++ b/src/NServiceBus.AcceptanceTests/EndpointTemplates/DefaultServer.cs @@ -2,20 +2,16 @@ { using System; using System.Collections.Generic; - using System.Linq; - using System.Reflection; + using System.Threading.Tasks; + using AcceptanceTesting.Customization; using AcceptanceTesting.Support; - using Hosting.Helpers; - using Logging; - using NServiceBus; - using NServiceBus.AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using Features; using NServiceBus.Config.ConfigurationSource; - using NServiceBus.Configuration.AdvanceExtensibility; + using NServiceBus.Serialization; public class DefaultServer : IEndpointSetupTemplate { - readonly List typesToInclude; - public DefaultServer() { typesToInclude = new List(); @@ -26,76 +22,44 @@ public DefaultServer(List typesToInclude) this.typesToInclude = typesToInclude; } - public BusConfiguration GetConfiguration(RunDescriptor runDescriptor, EndpointConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) + public async Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) { var settings = runDescriptor.Settings; - LogManager.UseFactory(new ContextAppender(runDescriptor.ScenarioContext, endpointConfiguration.EndpointName)); - - var types = GetTypesScopedByTestClass(endpointConfiguration); + var types = endpointConfiguration.GetTypesScopedByTestClass(); typesToInclude.AddRange(types); - var builder = new BusConfiguration(); - - builder.EndpointName(endpointConfiguration.EndpointName); - builder.TypesToScan(typesToInclude); - builder.CustomConfigurationSource(configSource); - builder.EnableInstallers(); - builder.DefineTransport(settings, endpointConfiguration.BuilderType); - builder.DefineTransactions(settings); - builder.DefineBuilder(settings); - builder.RegisterComponents(r => - { - r.RegisterSingleton(runDescriptor.ScenarioContext.GetType(), runDescriptor.ScenarioContext); - r.RegisterSingleton(typeof(ScenarioContext), runDescriptor.ScenarioContext); - }); - - - var serializer = settings.GetOrNull("Serializer"); - - if (serializer != null) - { - builder.UseSerialization(Type.GetType(serializer)); - } - builder.DefinePersistence(settings); - - builder.GetSettings().SetDefault("ScaleOut.UseSingleBrokerQueue", true); - configurationBuilderCustomization(builder); - - - return builder; - } - - static IEnumerable GetTypesScopedByTestClass(EndpointConfiguration endpointConfiguration) - { - var assemblies = new AssemblyScanner().GetScannableAssemblies(); - - var types = assemblies.Assemblies - //exclude all test types by default - .Where(a => a != Assembly.GetExecutingAssembly()) - .SelectMany(a => a.GetTypes()); + var configuration = new EndpointConfiguration(endpointConfiguration.EndpointName); + configuration.TypesToIncludeInScan(typesToInclude); + configuration.CustomConfigurationSource(configSource); + configuration.EnableInstallers(); - types = types.Union(GetNestedTypeRecursive(endpointConfiguration.BuilderType.DeclaringType, endpointConfiguration.BuilderType)); + configuration.DisableFeature(); - types = types.Union(endpointConfiguration.TypesToInclude); + var recoverability = configuration.Recoverability(); + recoverability.Delayed(delayed => delayed.NumberOfRetries(0)); + recoverability.Immediate(immediate => immediate.NumberOfRetries(0)); - return types.Where(t => !endpointConfiguration.TypesToExclude.Contains(t)).ToList(); - } - - static IEnumerable GetNestedTypeRecursive(Type rootType, Type builderType) - { - yield return rootType; + await configuration.DefineTransport(settings, endpointConfiguration.EndpointName).ConfigureAwait(false); - if (typeof(IEndpointConfigurationFactory).IsAssignableFrom(rootType) && rootType != builderType) - yield break; + configuration.DefineBuilder(settings); + configuration.RegisterComponentsAndInheritanceHierarchy(runDescriptor); - foreach (var nestedType in rootType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SelectMany(t => GetNestedTypeRecursive(t, builderType))) + Type serializerType; + if (settings.TryGet("Serializer", out serializerType)) { - yield return nestedType; + configuration.UseSerialization((SerializationDefinition)Activator.CreateInstance(serializerType)); } + await configuration.DefinePersistence(settings, endpointConfiguration.EndpointName).ConfigureAwait(false); + + configuration.GetSettings().SetDefault("ScaleOut.UseSingleBrokerQueue", true); + configurationBuilderCustomization(configuration); + + return configuration; } + List typesToInclude; } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/EndpointCustomizationConfigurationExtensions.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/EndpointCustomizationConfigurationExtensions.cs new file mode 100644 index 00000000000..3c14387b6eb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/EndpointTemplates/EndpointCustomizationConfigurationExtensions.cs @@ -0,0 +1,53 @@ +namespace NServiceBus.AcceptanceTests.EndpointTemplates +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using AcceptanceTesting.Support; + using NServiceBus.Hosting.Helpers; + + public static class EndpointCustomizationConfigurationExtensions + { + public static IEnumerable GetTypesScopedByTestClass(this EndpointCustomizationConfiguration endpointConfiguration) + { + var assemblies = new AssemblyScanner().GetScannableAssemblies(); + + var types = assemblies.Assemblies + //exclude all test types by default + .Where(a => + { + var references = a.GetReferencedAssemblies(); + + return references.All(an => an.Name != "nunit.framework"); + }) + .SelectMany(a => a.GetTypes()); + + types = types.Union(GetNestedTypeRecursive(endpointConfiguration.BuilderType.DeclaringType, endpointConfiguration.BuilderType)); + + types = types.Union(endpointConfiguration.TypesToInclude); + + return types.Where(t => !endpointConfiguration.TypesToExclude.Contains(t)).ToList(); + } + + static IEnumerable GetNestedTypeRecursive(Type rootType, Type builderType) + { + if (rootType == null) + { + throw new InvalidOperationException("Make sure you nest the endpoint infrastructure inside the TestFixture as nested classes"); + } + + yield return rootType; + + if (typeof(IEndpointConfigurationFactory).IsAssignableFrom(rootType) && rootType != builderType) + { + yield break; + } + + foreach (var nestedType in rootType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SelectMany(t => GetNestedTypeRecursive(t, builderType))) + { + yield return nestedType; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/EndpointTemplates/ServerWithNoDefaultPersistenceDefinitions.cs b/src/NServiceBus.AcceptanceTests/EndpointTemplates/ServerWithNoDefaultPersistenceDefinitions.cs new file mode 100644 index 00000000000..7e35ec41573 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/EndpointTemplates/ServerWithNoDefaultPersistenceDefinitions.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.AcceptanceTests.EndpointTemplates +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting.Customization; + using AcceptanceTesting.Support; + using Features; + using NServiceBus.Config.ConfigurationSource; + + public class ServerWithNoDefaultPersistenceDefinitions : IEndpointSetupTemplate + { + public ServerWithNoDefaultPersistenceDefinitions() + { + typesToInclude = new List(); + } + + public ServerWithNoDefaultPersistenceDefinitions(List typesToInclude) + { + this.typesToInclude = typesToInclude; + } + + public async Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) + { + var settings = runDescriptor.Settings; + + var types = endpointConfiguration.GetTypesScopedByTestClass(); + + typesToInclude.AddRange(types); + + var builder = new EndpointConfiguration(endpointConfiguration.EndpointName); + builder.TypesToIncludeInScan(typesToInclude); + builder.CustomConfigurationSource(configSource); + builder.EnableInstallers(); + + builder.DisableFeature(); + builder.Recoverability() + .Delayed(delayed => delayed.NumberOfRetries(0)) + .Immediate(immediate => immediate.NumberOfRetries(0)); + + await builder.DefineTransport(settings, endpointConfiguration.EndpointName).ConfigureAwait(false); + + builder.RegisterComponentsAndInheritanceHierarchy(runDescriptor); + + configurationBuilderCustomization(builder); + + return builder; + } + + List typesToInclude; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/SerializerCorrupter.cs b/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/SerializerCorrupter.cs deleted file mode 100644 index 16bcf302d8b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/SerializerCorrupter.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using System.Reflection; - - static class SerializerCorrupter - { - - public static void Corrupt() - { - var msmqUtilitiesType = Type.GetType("NServiceBus.MsmqUtilities, NServiceBus.Core"); - var headerSerializerField = msmqUtilitiesType.GetField("headerSerializer", BindingFlags.Static | BindingFlags.NonPublic); - headerSerializerField.SetValue(null, null); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage.cs b/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage.cs deleted file mode 100644 index 068674cd738..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_cant_convert_to_TransportMessage : NServiceBusAcceptanceTest - { - [Test] - public void Should_send_message_to_error_queue() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.Send(new Message()))) - .WithEndpoint() - .AllowExceptions() - .Done(c => c.GetAllLogs().Any(l=>l.Level == "error")) - .Repeat(r => r.For()) - .Should(c => - { - var logs = c.GetAllLogs(); - Assert.True(logs.Any(l => l.Message.Contains("is corrupt and will be moved to"))); - }) - .Run(new RunSettings - { - UseSeparateAppDomains = true - }); - } - - public class Context : ScenarioContext - { - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup() - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - SerializerCorrupter.Corrupt(); - EndpointSetup(); - } - - } - - [Serializable] - public class Message : IMessage - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_NoTransactions.cs b/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_NoTransactions.cs deleted file mode 100644 index 4d6bcb579a2..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_NoTransactions.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - using IMessage = NServiceBus.IMessage; - - public class When_cant_convert_to_TransportMessage_NoTransactions : NServiceBusAcceptanceTest - { - [Test] - public void Should_send_message_to_error_queue() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.Send(new Message()))) - .WithEndpoint() - .AllowExceptions() - .Done(c => c.GetAllLogs().Any(l=>l.Level == "error")) - .Repeat(r => r.For()) - .Should(c => - { - var logs = c.GetAllLogs(); - Assert.True(logs.Any(l => l.Message.Contains("is corrupt and will be moved to"))); - }) - .Run(new RunSettings - { - UseSeparateAppDomains = true - }); - } - - public class Context : ScenarioContext - { - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup(b => b.Transactions().Disable()) - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - SerializerCorrupter.Corrupt(); - EndpointSetup(b => b.Transactions().Disable()); - } - } - - [Serializable] - public class Message : IMessage - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_SuppressedDTC.cs b/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_SuppressedDTC.cs deleted file mode 100644 index 4803d40ef34..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/Cant_convert_to_TransportMessage/When_cant_convert_to_TransportMessage_SuppressedDTC.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_cant_convert_to_TransportMessage_SuppressedDTC : NServiceBusAcceptanceTest - { - [Test] - public void Should_send_message_to_error_queue() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.Send(new Message()))) - .WithEndpoint() - .AllowExceptions() - .Done(c => c.GetAllLogs().Any(l=>l.Level == "error")) - .Repeat(r => r.For()) - .Should(c => - { - var logs = c.GetAllLogs(); - Assert.True(logs.Any(l => l.Message.Contains("is corrupt and will be moved to"))); - }) - .Run(new RunSettings - { - UseSeparateAppDomains = true - }); - } - - public class Context : ScenarioContext - { - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup(b => b.Transactions().DisableDistributedTransactions()) - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - SerializerCorrupter.Corrupt(); - EndpointSetup(b => b.Transactions().DisableDistributedTransactions()); - } - } - - [Serializable] - public class Message : IMessage - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/Message_without_an_id.cs b/src/NServiceBus.AcceptanceTests/Exceptions/Message_without_an_id.cs deleted file mode 100644 index 56ea06560f3..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/Message_without_an_id.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NServiceBus.MessageMutator; - using NServiceBus.Unicast; - using NServiceBus.Unicast.Messages; - using NServiceBus.Unicast.Transport; - using NUnit.Framework; - - public class Message_without_an_id : NServiceBusAcceptanceTest - { - [Test] - public void Should_invoke_start_message_processing_listeners() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint() - .Done(c => c.StartMessageProcessingCalled) - .Run(); - - Assert.IsTrue(context.StartMessageProcessingCalled); - } - - public class Context : ScenarioContext - { - public bool StartMessageProcessingCalled { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup() - .WithConfig(c => - { - c.MaxRetries = 0; - }); - } - - class StartProcessingListener : IWantToRunWhenBusStartsAndStops - { - readonly UnicastBus bus; - Context context; - - public StartProcessingListener(UnicastBus bus, Context context) - { - this.bus = bus; - this.context = context; - bus.Transport.StartedMessageProcessing += transport_StartedMessageProcessing; - } - - void transport_StartedMessageProcessing(object sender, StartedMessageProcessingEventArgs e) - { - context.StartMessageProcessingCalled = true; - } - - public void Start() - { - bus.SendLocal(new Message()); - } - - public void Stop() - { - bus.Transport.StartedMessageProcessing -= transport_StartedMessageProcessing; - } - } - - class CorruptionMutator : IMutateOutgoingTransportMessages - { - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - transportMessage.Headers[Headers.MessageId] = ""; - } - } - - class Handler : IHandleMessages - { - public void Handle(Message message) - { - } - } - } - - [Serializable] - public class Message : IMessage - { - } - } - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/StackTraceAssert.cs b/src/NServiceBus.AcceptanceTests/Exceptions/StackTraceAssert.cs deleted file mode 100644 index d3f49c759e6..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/StackTraceAssert.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using NUnit.Framework; - -namespace NServiceBus.AcceptanceTests.Exceptions -{ - static class StackTraceAssert - { - public static void StartsWith(string expected, string actual) - { - if (actual == null) - { - Assert.Fail(); - } - else - { - var cleanStackTrace = CleanStackTrace(actual); - - var reader = new StringReader(cleanStackTrace); - - var stringBuilder = new StringBuilder(); - while (true) - { - var actualLine = reader.ReadLine(); - if (actualLine == null) - { - break; - } - if (expected.Contains(actualLine)) - { - stringBuilder.AppendLine(actualLine); - } - } - - try - { - actual = stringBuilder.ToString().TrimEnd(); - Assert.AreEqual(actual, expected); - } - catch (Exception) - { - Trace.WriteLine(cleanStackTrace); - throw; - } - } - } - static string CleanStackTrace(string stackTrace) - { - if (stackTrace== null) - { - return string.Empty; - } - using (var stringReader = new StringReader(stackTrace)) - { - var stringBuilder = new StringBuilder(); - while (true) - { - var line = stringReader.ReadLine(); - if (line == null) - { - break; - } - - stringBuilder.AppendLine(line.Split(new[] - { - " in " - }, StringSplitOptions.RemoveEmptyEntries).First().Trim()); - } - return stringBuilder.ToString().Trim(); - } - } - } -} - diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/When_handler_throws_serialization_exception.cs b/src/NServiceBus.AcceptanceTests/Exceptions/When_handler_throws_serialization_exception.cs deleted file mode 100644 index 85e0b45b0ba..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/When_handler_throws_serialization_exception.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using System.Collections.Generic; - using System.Runtime.Serialization; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - - public class When_handler_throws_serialization_exception : NServiceBusAcceptanceTest - { - public static Func MaxNumberOfRetries = () => 5; - - [Test] - public void Should_retry_the_message_using_flr() - { - var context = new Context { Id = Guid.NewGuid() }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, ctx) => bus.SendLocal(new MessageToBeRetried { ContextId = ctx.Id }))) - .AllowExceptions() - .Done(c => c.HandedOverToSlr) - .Run(TimeSpan.FromMinutes(5)); - - Assert.AreEqual(MaxNumberOfRetries(), context.NumberOfTimesInvoked); - Assert.IsFalse(context.SerializationFailedCalled); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - public int NumberOfTimesInvoked { get; set; } - public bool HandedOverToSlr { get; set; } - public Dictionary HeadersOfTheFailedMessage { get; set; } - public bool SerializationFailedCalled { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance))); - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - Context.SerializationFailedCalled = true; - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.HandedOverToSlr = true; - Context.HeadersOfTheFailedMessage = message.Headers; - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.ContextId != Context.Id) - { - return; - } - Context.NumberOfTimesInvoked++; - throw new SerializationException(); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid ContextId { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Exceptions/When_serialization_throws.cs b/src/NServiceBus.AcceptanceTests/Exceptions/When_serialization_throws.cs deleted file mode 100644 index 11cf288a6e8..00000000000 --- a/src/NServiceBus.AcceptanceTests/Exceptions/When_serialization_throws.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Exceptions -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NServiceBus.Faults; - using NServiceBus.Features; - using NServiceBus.MessageMutator; - using NServiceBus.Unicast.Messages; - using NUnit.Framework; - - public class When_serialization_throws : NServiceBusAcceptanceTest - { - [Test] - public void Should_receive_MessageDeserializationException() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new Message()))) - .AllowExceptions() - .Done(c => c.ExceptionReceived) - .Run(); - - Assert.AreEqual(typeof(MessageDeserializationException), context.ExceptionType); - } - - public class Context : ScenarioContext - { - public bool ExceptionReceived { get; set; } - public string StackTrace { get; set; } - public Type ExceptionType { get; set; } - public string InnerExceptionStackTrace { get; set; } - public Type InnerExceptionType { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(b => - { - b.RegisterComponents(c => - { - c.ConfigureComponent(DependencyLifecycle.SingleInstance); - c.ConfigureComponent(DependencyLifecycle.InstancePerCall); - }); - b.DisableFeature(); - }) - .WithConfig(c => - { - c.MaxRetries = 0; - }); - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - Context.ExceptionType = e.GetType(); - Context.StackTrace = e.StackTrace; - if (e.InnerException != null) - { - Context.InnerExceptionType = e.InnerException.GetType(); - Context.InnerExceptionStackTrace = e.InnerException.StackTrace; - } - Context.ExceptionReceived = true; - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - } - - public void Init(Address address) - { - } - } - - class CorruptionMutator : IMutateTransportMessages - { - public void MutateIncoming(TransportMessage transportMessage) - { - transportMessage.Body[1]++; - } - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - } - } - - class Handler : IHandleMessages - { - public void Handle(Message message) - { - } - } - } - - [Serializable] - public class Message : IMessage - { - } - } - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Forwarding/When_forwarding_is_configured_for_endpoint.cs b/src/NServiceBus.AcceptanceTests/Forwarding/When_forwarding_is_configured_for_endpoint.cs new file mode 100644 index 00000000000..ca21eed339f --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Forwarding/When_forwarding_is_configured_for_endpoint.cs @@ -0,0 +1,75 @@ +namespace NServiceBus.AcceptanceTests.Forwarding +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_forwarding_is_configured_for_endpoint : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_forward_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToForward()))) + .WithEndpoint() + .Done(c => c.GotForwardedMessage) + .Run(); + + Assert.IsTrue(context.GotForwardedMessage); + CollectionAssert.AreEqual(context.ForwardedHeaders, context.ReceivedHeaders, "Headers should be preserved on the forwarded message"); + } + + public class Context : ScenarioContext + { + public bool GotForwardedMessage { get; set; } + public IReadOnlyDictionary ForwardedHeaders { get; set; } + public IReadOnlyDictionary ReceivedHeaders { get; set; } + } + + public class ForwardReceiver : EndpointConfigurationBuilder + { + public ForwardReceiver() + { + EndpointSetup() + .CustomEndpointName("endpoint_forward_receiver"); + } + + public class MessageToForwardHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToForward message, IMessageHandlerContext context) + { + Context.ForwardedHeaders = context.MessageHeaders; + Context.GotForwardedMessage = true; + return Task.FromResult(0); + } + } + } + + public class EndpointThatForwards : EndpointConfigurationBuilder + { + public EndpointThatForwards() + { + EndpointSetup(c => c.ForwardReceivedMessagesTo("endpoint_forward_receiver")); + } + + public class MessageToForwardHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToForward message, IMessageHandlerContext context) + { + Context.ReceivedHeaders = context.MessageHeaders; + return Task.FromResult(0); + } + } + } + + public class MessageToForward : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Forwarding/When_requesting_message_to_be_forwarded.cs b/src/NServiceBus.AcceptanceTests/Forwarding/When_requesting_message_to_be_forwarded.cs new file mode 100644 index 00000000000..308fb6d1267 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Forwarding/When_requesting_message_to_be_forwarded.cs @@ -0,0 +1,75 @@ +namespace NServiceBus.AcceptanceTests.Forwarding +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_requesting_message_to_be_forwarded : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_forward_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToForward()))) + .WithEndpoint() + .Done(c => c.GotForwardedMessage) + .Run(); + + Assert.IsTrue(context.GotForwardedMessage); + CollectionAssert.AreEqual(context.ForwardedHeaders, context.ReceivedHeaders, "Headers should be preserved on the forwarded message"); + } + + public class Context : ScenarioContext + { + public bool GotForwardedMessage { get; set; } + public IReadOnlyDictionary ForwardedHeaders { get; set; } + public IReadOnlyDictionary ReceivedHeaders { get; set; } + } + + public class ForwardReceiver : EndpointConfigurationBuilder + { + public ForwardReceiver() + { + EndpointSetup() + .CustomEndpointName("message_forward_receiver"); + } + + public class MessageToForwardHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToForward message, IMessageHandlerContext context) + { + Context.ForwardedHeaders = context.MessageHeaders; + Context.GotForwardedMessage = true; + return Task.FromResult(0); + } + } + } + + public class EndpointThatForwards : EndpointConfigurationBuilder + { + public EndpointThatForwards() + { + EndpointSetup(); + } + + public class MessageToForwardHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToForward message, IMessageHandlerContext context) + { + Context.ReceivedHeaders = context.MessageHeaders; + return context.ForwardCurrentMessageTo("message_forward_receiver"); + } + } + } + + public class MessageToForward : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/HostInformation/When_a_message_is_received.cs b/src/NServiceBus.AcceptanceTests/HostInformation/When_a_message_is_received.cs deleted file mode 100644 index afb123d8517..00000000000 --- a/src/NServiceBus.AcceptanceTests/HostInformation/When_a_message_is_received.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace NServiceBus.AcceptanceTests.HostInformation -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - using Unicast; - - public class When_a_message_is_received : NServiceBusAcceptanceTest - { - static Guid hostId = new Guid("39365055-daf2-439e-b84d-acbef8fd803d"); - const string displayName = "FooBar"; - - [Test] - public void Host_information_should_be_available_in_headers() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(e => e.Given(b => b.SendLocal(new MyMessage()))) - .Done(c => c.HostId != Guid.Empty) - .Run(); - - Assert.AreEqual(hostId, context.HostId); - Assert.AreEqual(displayName, context.HostDisplayName); - } - - public class Context : ScenarioContext - { - public Guid HostId { get; set; } - public string HostDisplayName { get; set; } - } - - [Serializable] - public class MyMessage : ICommand - { - } - - public class MyMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public Context Context { get; set; } - - public void Handle(MyMessage message) - { - Context.HostId = new Guid(Bus.GetMessageHeader(message, Headers.HostId)); - Context.HostDisplayName = Bus.GetMessageHeader(message, Headers.HostDisplayName); - } - } - - public class MyEndpoint : EndpointConfigurationBuilder - { - public MyEndpoint() - { - EndpointSetup(); - } - } - - class OverrideHostInformation : IWantToRunWhenConfigurationIsComplete - { - public UnicastBus UnicastBus { get; set; } - - public void Run(Configure config) - { - var hostInformation = new Hosting.HostInformation(hostId, displayName); -#pragma warning disable 618 - UnicastBus.HostInformation = hostInformation; -#pragma warning restore 618 - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/HostInformation/When_customising_hostinfo.cs b/src/NServiceBus.AcceptanceTests/HostInformation/When_customising_hostinfo.cs deleted file mode 100644 index 5f25fa2e352..00000000000 --- a/src/NServiceBus.AcceptanceTests/HostInformation/When_customising_hostinfo.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace NServiceBus.AcceptanceTests.HostInformation -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Utils; - using NUnit.Framework; - - public class When_customising_hostinfo : NServiceBusAcceptanceTest - { - static Guid hostId = new Guid("6c0f50de-dac9-4693-b138-6d1033c15ed6"); - static string instanceName = "Foo"; - static string hostName = "Bar"; - - [Test] - public void UsingCustomIdentifier() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(e => e.Given(b => b.SendLocal(new MyMessage()))) - .Done(c => c.HostId != Guid.Empty) - .Run(); - - Assert.AreEqual(hostId, context.HostId); - } - - [Test] - public void UsingNames() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(e => e.Given(b => b.SendLocal(new MyMessage()))) - .Done(c => c.HostId != Guid.Empty) - .Run(); - - Assert.AreEqual(DeterministicGuid.Create(instanceName, hostName), context.HostId); - } - - public class UsingNames_Endpoint : EndpointConfigurationBuilder - { - public UsingNames_Endpoint() - { - EndpointSetup(b => b.UniquelyIdentifyRunningInstance().UsingNames(instanceName, hostName)); - } - } - - public class UsingCustomIdentifier_Endpoint : EndpointConfigurationBuilder - { - public UsingCustomIdentifier_Endpoint() - { - EndpointSetup(b => b.UniquelyIdentifyRunningInstance().UsingCustomIdentifier(hostId)); - } - } - - public class MyMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public Context Context { get; set; } - - public void Handle(MyMessage message) - { - Context.HostDisplayName = Bus.GetMessageHeader(message, Headers.HostDisplayName); - Context.HostId = new Guid(Bus.GetMessageHeader(message, Headers.HostId)); - } - } - - public class Context : ScenarioContext - { - public Guid HostId { get; set; } - public string HostDisplayName { get; set; } - } - - [Serializable] - public class MyMessage : ICommand - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostid.cs b/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostid.cs deleted file mode 100644 index 81cff1312e7..00000000000 --- a/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostid.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace NServiceBus.AcceptanceTests.HostInformation -{ - using System; - using System.Collections.Concurrent; - using System.Reflection; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_feature_overrides_hostid : NServiceBusAcceptanceTest - { - - [Test] - public void MD5_should_not_be_used() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(e => e.Given(b => b.SendLocal(new MyMessage()))) - .Done(c => c.Done) - .Run(); - - Assert.IsTrue(context.NotSet); - } - - public class MyEndpoint : EndpointConfigurationBuilder - { - public MyEndpoint() - { - EndpointSetup(c => c.UniquelyIdentifyRunningInstance().UsingCustomIdentifier(Guid.NewGuid())); - } - } - - public class MyFeatureThatOverridesHostInformationDefaults : Feature - { - bool notSet; - - public MyFeatureThatOverridesHostInformationDefaults() - { - EnableByDefault(); - DependsOn("UnicastBus"); - Defaults(s => - { - // remove the override, we need to hack it via reflection! - var fieldInfo = s.GetType().GetField("Overrides", BindingFlags.Instance | BindingFlags.NonPublic); - var dictionary = (ConcurrentDictionary)fieldInfo.GetValue(s); - object s2; - dictionary.TryRemove("NServiceBus.HostInformation.HostId", out s2); - - // Try to get value, setting should not exist - notSet = !s.HasSetting("NServiceBus.HostInformation.HostId"); - - // Set override again so we have something - s.Set("NServiceBus.HostInformation.HostId", Guid.NewGuid()); - - }); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureProperty(c => c.NotSet, notSet); - } - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyMessage message) - { - Context.Done = true; - } - } - - public class Context : ScenarioContext - { - public bool NotSet { get; set; } - public bool Done { get; set; } - } - - [Serializable] - public class MyMessage : ICommand - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostinfo.cs b/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostinfo.cs deleted file mode 100644 index 3b28f9b9288..00000000000 --- a/src/NServiceBus.AcceptanceTests/HostInformation/When_feature_overrides_hostinfo.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace NServiceBus.AcceptanceTests.HostInformation -{ - using System; - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_feature_overrides_hostinfo : NServiceBusAcceptanceTest - { - static Guid hostId = new Guid("6c0f50de-dac9-4693-b138-6d1033c15ed6"); - static string instanceName = "Foo"; - - [Test] - public void HostInfo_is_changed() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(e => e.Given(b => b.SendLocal(new MyMessage()))) - .Done(c => c.HostId != Guid.Empty) - .Run(); - - Assert.AreEqual(hostId, context.HostId); - Assert.AreEqual(instanceName, context.HostDisplayName); - } - - public class MyEndpoint : EndpointConfigurationBuilder - { - public MyEndpoint() - { - EndpointSetup(); - } - } - - public class MyFeatureThatOverridesHostInformationDefaults : Feature - { - public MyFeatureThatOverridesHostInformationDefaults() - { - EnableByDefault(); - DependsOn("UnicastBus"); - Defaults(s => - { - s.SetDefault("NServiceBus.HostInformation.HostId", hostId); - s.SetDefault("NServiceBus.HostInformation.DisplayName", instanceName); - s.SetDefault("NServiceBus.HostInformation.Properties", new Dictionary - { - {"RoleName", "My role name"}, - {"RoleInstanceId", "the role instance id"}, - }); - }); - } - - protected override void Setup(FeatureConfigurationContext context) - { - } - } - - public class MyMessageHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public Context Context { get; set; } - - public void Handle(MyMessage message) - { - Context.HostDisplayName = Bus.GetMessageHeader(message, Headers.HostDisplayName); - Context.HostId = new Guid(Bus.GetMessageHeader(message, Headers.HostId)); - } - } - - public class Context : ScenarioContext - { - public Guid HostId { get; set; } - public string HostDisplayName { get; set; } - } - - [Serializable] - public class MyMessage : ICommand - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_audited.cs new file mode 100644 index 00000000000..7a09fb528e5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_audited.cs @@ -0,0 +1,80 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_a_message_is_audited : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_add_host_related_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.IsNotNull(context.HostId); + Assert.IsNotNull(context.HostName); + Assert.IsNotNull(context.Endpoint); + Assert.IsNotNull(context.Machine); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string HostId { get; set; } + public string HostName { get; set; } + public string Endpoint { get; set; } + public string Machine { get; set; } + } + + public class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup() + .AuditTo(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + class AuditSpyEndpoint : EndpointConfigurationBuilder + { + public AuditSpyEndpoint() + { + EndpointSetup(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + TestContext.HostId = context.MessageHeaders[Headers.HostId]; + TestContext.HostName = context.MessageHeaders[Headers.HostDisplayName]; + TestContext.Endpoint = context.MessageHeaders[Headers.ProcessingEndpoint]; + TestContext.Machine = context.MessageHeaders[Headers.ProcessingMachine]; + TestContext.Done = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_faulted.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_faulted.cs new file mode 100644 index 00000000000..0b9a181efec --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_a_message_is_faulted.cs @@ -0,0 +1,80 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_a_message_is_faulted : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_add_host_related_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageThatFails())).DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.IsNotNull(context.HostId, "Host Id should be included in fault message headers"); + Assert.IsNotNull(context.HostName, "Host Name should be included in fault message headers"); + Assert.IsNotNull(context.Endpoint, "Endpoint name should be included in fault message headers."); + Assert.IsNotNull(context.Machine, "Machine should be included in fault message headers."); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string HostId { get; set; } + public string HostName { get; set; } + public string Endpoint { get; set; } + public string Machine { get; set; } + } + + public class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup(c => + { + c.SendFailedMessagesTo("errorQueueForAcceptanceTest"); + }); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + class EndpointThatHandlesErrorMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesErrorMessages() + { + EndpointSetup().CustomEndpointName("errorQueueForAcceptanceTest"); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + TestContext.HostId = context.MessageHeaders.ContainsKey(Headers.HostId) ? context.MessageHeaders[Headers.HostId] : null; + TestContext.HostName = context.MessageHeaders.ContainsKey(Headers.HostDisplayName) ? context.MessageHeaders[Headers.HostDisplayName] : null; + TestContext.Endpoint = context.MessageHeaders.ContainsKey(Headers.ProcessingEndpoint) ? context.MessageHeaders[Headers.ProcessingEndpoint] : null; + TestContext.Machine = context.MessageHeaders.ContainsKey(Headers.ProcessingMachine) ? context.MessageHeaders[Headers.ProcessingMachine] : null; + TestContext.Done = true; + return Task.FromResult(0); + } + } + } + + public class MessageThatFails : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostid.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostid.cs new file mode 100644 index 00000000000..692177f1bcf --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostid.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System; + using System.Collections.Concurrent; + using System.Reflection; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_feature_overrides_hostid : NServiceBusAcceptanceTest + { + [Test] + public async Task MD5_should_not_be_used() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.Done) + .Run(); + + Assert.IsTrue(context.NotSet); + } + + public class MyEndpoint : EndpointConfigurationBuilder + { + public MyEndpoint() + { + EndpointSetup(c => c.UniquelyIdentifyRunningInstance().UsingCustomIdentifier(Guid.NewGuid())); + } + } + + public class MyFeatureThatOverridesHostInformationDefaults : Feature + { + public MyFeatureThatOverridesHostInformationDefaults() + { + EnableByDefault(); + Defaults(s => + { + // remove the override, we need to hack it via reflection! + var fieldInfo = s.GetType().GetField("Overrides", BindingFlags.Instance | BindingFlags.NonPublic); + var dictionary = (ConcurrentDictionary) fieldInfo.GetValue(s); + object s2; + dictionary.TryRemove("NServiceBus.HostInformation.HostId", out s2); + + // Try to get value, setting should not exist + var context = s.Get(); + context.NotSet = !s.HasSetting("NServiceBus.HostInformation.HostId"); + + // Set override again so we have something + s.Set("NServiceBus.HostInformation.HostId", Guid.NewGuid()); + }); + } + + protected override void Setup(FeatureConfigurationContext context) + { + } + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.Done = true; + + return Task.FromResult(0); + } + } + + public class Context : ScenarioContext + { + public bool NotSet { get; set; } + public bool Done { get; set; } + } + + [Serializable] + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostinfo.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostinfo.cs new file mode 100644 index 00000000000..9daaf599e92 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_feature_overrides_hostinfo.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_feature_overrides_hostinfo : NServiceBusAcceptanceTest + { + [Test] + public async Task HostInfo_is_changed() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.OriginatingHostId != Guid.Empty) + .Run(); + + Assert.AreEqual(hostId, context.OriginatingHostId); + } + + static Guid hostId = new Guid("6c0f50de-dac9-4693-b138-6d1033c15ed6"); + static string instanceName = "Foo"; + + public class MyEndpoint : EndpointConfigurationBuilder + { + public MyEndpoint() + { + EndpointSetup(); + } + } + + public class MyFeatureThatOverridesHostInformationDefaults : Feature + { + public MyFeatureThatOverridesHostInformationDefaults() + { + EnableByDefault(); + Defaults(s => + { + s.SetDefault("NServiceBus.HostInformation.HostId", hostId); + s.SetDefault("NServiceBus.HostInformation.DisplayName", instanceName); + s.SetDefault("NServiceBus.HostInformation.Properties", new Dictionary + { + {"RoleName", "My role name"}, + {"RoleInstanceId", "the role instance id"} + }); + }); + } + + protected override void Setup(FeatureConfigurationContext context) + { + } + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + TestContext.OriginatingHostId = new Guid(context.MessageHeaders[Headers.OriginatingHostId]); + return Task.FromResult(0); + } + } + + public class Context : ScenarioContext + { + public Guid OriginatingHostId { get; set; } + } + + [Serializable] + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_overriding_input_queue_name.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_overriding_input_queue_name.cs new file mode 100644 index 00000000000..ce32c587d15 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_overriding_input_queue_name.cs @@ -0,0 +1,57 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_overriding_input_queue_name : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_custom_queue_names() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.Done) + .Run(); + + Assert.IsTrue(context.Done); + Assert.IsTrue(context.InputQueue.StartsWith("OverriddenInputQueue")); + } + + public class MyEndpoint : EndpointConfigurationBuilder + { + public MyEndpoint() + { + EndpointSetup((c, d) => + { + c.OverrideLocalAddress("OverriddenInputQueue"); + c.EnableFeature(); + }); + } + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.InputQueue = context.MessageHeaders[Headers.ReplyToAddress]; + Context.Done = true; + return Task.FromResult(0); + } + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public string InputQueue { get; set; } + } + + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Hosting/When_sending_ensure_proper_headers.cs b/src/NServiceBus.AcceptanceTests/Hosting/When_sending_ensure_proper_headers.cs new file mode 100644 index 00000000000..bccf06d5c7c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Hosting/When_sending_ensure_proper_headers.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Hosting +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_ensure_proper_headers : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_have_proper_headers_for_the_originating_endpoint() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.Send(m => { m.Id = c.Id; }))) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(); + + Assert.True(context.WasCalled, "The message handler should be called"); + Assert.AreEqual("SenderForEnsureProperHeadersTest", context.ReceivedHeaders[Headers.OriginatingEndpoint], "Message should contain the Originating endpoint"); + Assert.That(context.ReceivedHeaders[Headers.OriginatingHostId], Is.Not.Null.Or.Empty, "OriginatingHostId cannot be null or empty"); + Assert.That(context.ReceivedHeaders[Headers.OriginatingMachine], Is.Not.Null.Or.Empty, "Endpoint machine name cannot be null or empty"); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public IReadOnlyDictionary ReceivedHeaders { get; set; } + public Guid Id { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + CustomEndpointName("SenderForEnsureProperHeadersTest"); + EndpointSetup() + .AddMapping(typeof(Receiver)); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(); + } + } + + [Serializable] + public class MyMessage : ICommand + { + public Guid Id { get; set; } + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (TestContext.Id != message.Id) + { + return Task.FromResult(0); + } + + TestContext.ReceivedHeaders = context.MessageHeaders; + TestContext.WasCalled = true; + + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Licensing/When_a_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Licensing/When_a_message_is_audited.cs new file mode 100644 index 00000000000..d5d1900a0e4 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Licensing/When_a_message_is_audited.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.AcceptanceTests.Licensing +{ + using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Logging; + using NUnit.Framework; + + public class When_a_message_is_audited : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_add_the_license_diagnostic_headers() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.IsTrue(context.HasDiagnosticLicensingHeaders); + + if (Debugger.IsAttached) + { + Assert.True(context.Logs.Any(m => m.Level == LogLevel.Error && m.Message.StartsWith("Your license has expired")), "Error should be logged"); + } + } + + static string ExpiredLicense = @" + + Ultimate Test + + + + + + + + + + kz07xp2x3tjk+ixQglCHq40RJg8= + + + WN0zCL3i2vvwtPFI7/Qbo8ymhJFeYpauFqFbuFynOfWrKd5PMfcY1ToWZyz1vs6dLFL9kPngtVRX9yZZXC1y6la8oS/rnBq0Jwm2pFqCtIVtXKee93dTTx7Bij9x7XUBtAVpZDszbZPfLnrdHwS4BFn4CTvOJRiSUEB1ks1ONiQ= + + +"; + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool HasDiagnosticLicensingHeaders { get; set; } + } + + public class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup(c => c.License(ExpiredLicense)) + .AuditTo(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + class AuditSpyEndpoint : EndpointConfigurationBuilder + { + public AuditSpyEndpoint() + { + EndpointSetup(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + string licenseExpired; + + TestContext.HasDiagnosticLicensingHeaders = context.MessageHeaders.TryGetValue(Headers.HasLicenseExpired, out licenseExpired); + + TestContext.Done = true; + + return Task.FromResult(0); + } + } + } + + public class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/MessageDrivenPubSubRoutingExtensions.cs b/src/NServiceBus.AcceptanceTests/MessageDrivenPubSubRoutingExtensions.cs new file mode 100644 index 00000000000..26eb4e0f260 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/MessageDrivenPubSubRoutingExtensions.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using Configuration.AdvanceExtensibility; + using NServiceBus.Routing; + using Settings; + using Transport; + + public static class MessageDrivenPubSubRoutingExtensions + { + public static RoutingSettings MessageDrivenPubSubRouting(this EndpointConfiguration endpointConfiguration) + { + return new RoutingSettings(endpointConfiguration.GetSettings()); + } + + public class MessageDrivenPubSubTransportDefinition : TransportDefinition, IMessageDrivenSubscriptionTransport + { + public override string ExampleConnectionStringForErrorMessage { get; } + + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + throw new System.NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_empty_id_header.cs b/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_empty_id_header.cs new file mode 100644 index 00000000000..1c55e28f162 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_empty_id_header.cs @@ -0,0 +1,70 @@ +namespace NServiceBus.AcceptanceTests.MessageId +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_message_has_empty_id_header : NServiceBusAcceptanceTest + { + [Test] + public async Task A_message_id_is_generated_by_the_transport_layer() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.When(b => b.SendLocal(new Message()))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.IsFalse(string.IsNullOrWhiteSpace(context.MessageId)); + Assert.AreEqual(context.MessageId, context.Headers[Headers.MessageId], "Should populate the NServiceBus.MessageId header with the new value"); + } + + class CorruptionBehavior : IBehavior + { + public Task Invoke(IDispatchContext context, Func next) + { + context.Operations.First().Message.Headers[Headers.MessageId] = ""; + + return next(context); + } + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public string MessageId { get; set; } + public IReadOnlyDictionary Headers { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => c.Pipeline.Register("CorruptionBehavior", new CorruptionBehavior(), "Corrupting the message id")); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(Message message, IMessageHandlerContext context) + { + TestContext.MessageId = context.MessageId; + TestContext.Headers = context.MessageHeaders; + TestContext.MessageReceived = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class Message : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_no_id_header.cs b/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_no_id_header.cs new file mode 100644 index 00000000000..ccff3e7d686 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/MessageId/When_message_has_no_id_header.cs @@ -0,0 +1,66 @@ +namespace NServiceBus.AcceptanceTests.MessageId +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_message_has_no_id_header : NServiceBusAcceptanceTest + { + [Test] + public async Task A_message_id_is_generated_by_the_transport_layer_on_receiving_side() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.When(b => b.SendLocal(new Message()))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.IsFalse(string.IsNullOrWhiteSpace(context.MessageId)); + } + + class CorruptionBehavior : IBehavior + { + public Task Invoke(IDispatchContext context, Func next) + { + context.Operations.First().Message.Headers[Headers.MessageId] = null; + + return next(context); + } + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public string MessageId { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => c.Pipeline.Register("CorruptionBehavior", new CorruptionBehavior(), "Corrupting the message id")); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(Message message, IMessageHandlerContext context) + { + TestContext.MessageId = context.MessageId; + TestContext.MessageReceived = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class Message : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_Audit_OverrideTimeToBeReceived_set_and_transactional_Msmq.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_Audit_OverrideTimeToBeReceived_set_and_transactional_Msmq.cs deleted file mode 100644 index d7ae42d02f0..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_Audit_OverrideTimeToBeReceived_set_and_transactional_Msmq.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Config; - using NUnit.Framework; - - public class When_Audit_OverrideTimeToBeReceived_set_and_transactional_Msmq : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_not_start_and_show_error() - { - var context = new Context(); - var scenarioException = Assert.Throws(() => Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Repeat(r => r.For()) - .Run()) - .InnerException as ScenarioException; - - Assert.IsFalse(context.EndpointsStarted); - Assert.IsNotNull(scenarioException); - StringAssert.Contains("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ.", scenarioException.InnerException.Message); - } - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.Transactions().Enable(); - }) - .WithConfig(c => c.OverrideTimeToBeReceived = TimeSpan.FromHours(1)); - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceivedOnForwardedMessages_set_and_transactional_Msmq.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceivedOnForwardedMessages_set_and_transactional_Msmq.cs deleted file mode 100644 index f0e98226b93..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceivedOnForwardedMessages_set_and_transactional_Msmq.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Config; - using NUnit.Framework; - - public class When_TimeToBeReceivedOnForwardedMessages_set_and_transactional_Msmq : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_not_start_and_show_error() - { - var context = new Context(); - var scenarioException = Assert.Throws(() => Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Repeat(r => r.For()) - .Run()) - .InnerException as ScenarioException; - - Assert.IsFalse(context.EndpointsStarted); - Assert.IsNotNull(scenarioException); - StringAssert.Contains("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ.", scenarioException.InnerException.Message); - } - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.Transactions().Enable(); - }) - .WithConfig(c => c.TimeToBeReceivedOnForwardedMessages = TimeSpan.FromHours(1)); - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_DTC_Msmq.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_DTC_Msmq.cs deleted file mode 100644 index 8fedbf6b710..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_DTC_Msmq.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System; - using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_TimeToBeReceived_set_and_DTC_Msmq : NServiceBusAcceptanceTest - { - [Test] - public void Should_throw_on_send() - { - Scenario.Define() - .WithEndpoint(b => b.Given((bus, c) => - { - var exception = Assert.Throws(() => - { - using (new TransactionScope(TransactionScopeOption.Required)) - { - bus.SendLocal(new MyMessage()); - } - }); - Assert.IsTrue(exception.Message.EndsWith("Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ.")); - })) - .Repeat(r => r.For()) - .Run(); - } - - public class Context : ScenarioContext - { - } - public class TransactionalEndpoint : EndpointConfigurationBuilder - { - public TransactionalEndpoint() - { - EndpointSetup(c => c.Transactions().Enable().EnableDistributedTransactions()); - } - } - - [Serializable] - [TimeToBeReceived("00:00:10")] - public class MyMessage : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_receivetransaction_Msmq.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_receivetransaction_Msmq.cs deleted file mode 100644 index fb03e153634..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_TimeToBeReceived_set_and_receivetransaction_Msmq.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_TimeToBeReceived_set_and_receivetransaction_Msmq : NServiceBusAcceptanceTest - { - [Test] - public void Should_throw_on_send() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Repeat(r => r.For()) - .Run(); - Assert.IsTrue(context.CorrectExceptionThrown); - } - - public class Context : ScenarioContext - { - public bool CorrectExceptionThrown { get; set; } - } - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(c => c.Transactions().Enable().DisableDistributedTransactions()); - } - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - try - { - Bus.SendLocal(new MyTimeToBeReceivedMessage()); - } - catch (Exception ex) - { - Context.CorrectExceptionThrown = ex.Message.EndsWith("Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ."); - } - - } - } - } - - [Serializable] - public class MyMessage : IMessage - { - } - - [Serializable] - [TimeToBeReceived("00:01:00")] - public class MyTimeToBeReceivedMessage : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_publishing_with_authorizer.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_publishing_with_authorizer.cs deleted file mode 100644 index 040751af9d8..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_publishing_with_authorizer.cs +++ /dev/null @@ -1,152 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.PubSub; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_publishing_with_authorizer : NServiceBusAcceptanceTest - { - [Test] - public void Should_only_deliver_to_authorized() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscriber1Subscribed && c.Subscriber2Subscribed, (bus, c) => bus.Publish(new MyEvent())) - ) - .WithEndpoint(b => b.When(bus => - { - bus.Subscribe(); - })) - .WithEndpoint(b => b.When(bus => - { - bus.Subscribe(); - })) - .Done(c => - c.Subscriber1GotTheEvent && - c.DeclinedSubscriber2) - .Repeat(r => r.For(Transports.Msmq)) - .Should(c => - { - Assert.True(c.Subscriber1GotTheEvent); - Assert.False(c.Subscriber2GotTheEvent); - }) - .Run(); - } - - public class TestContext : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - public bool Subscriber2GotTheEvent { get; set; } - public bool Subscriber1Subscribed { get; set; } - public bool Subscriber2Subscribed { get; set; } - public bool DeclinedSubscriber2 { get; set; } - } - - class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => - { - b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.Subscriber1Subscribed = true; - } - - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber2")) - { - context.Subscriber2Subscribed = true; - } - }); - b.DisableFeature(); - }); - } - - public class SubscriptionAuthorizer : IAuthorizeSubscriptions - { - TestContext context; - - public SubscriptionAuthorizer(TestContext context) - { - this.context = context; - } - - public bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - var isFromSubscriber1 = headers["NServiceBus.ReplyToAddress"] - .Contains("Subscriber1"); - if (!isFromSubscriber1) - { - context.DeclinedSubscriber2 = true; - } - return isFromSubscriber1; - } - - public bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - return true; - } - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - TestContext context; - - public MyEventHandler(TestContext context) - { - this.context = context; - } - - public void Handle(MyEvent message) - { - context.Subscriber1GotTheEvent = true; - } - } - - - } - - public class Subscriber2 : EndpointConfigurationBuilder - { - public Subscriber2() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - TestContext context; - - public MyEventHandler(TestContext context) - { - this.context = context; - } - - public void Handle(MyEvent messageThatIsEnlisted) - { - context.Subscriber2GotTheEvent = true; - } - } - } - - public class MyEvent : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Msmq/When_unsubscribing_with_authorizer.cs b/src/NServiceBus.AcceptanceTests/Msmq/When_unsubscribing_with_authorizer.cs deleted file mode 100644 index 9b34a250ff6..00000000000 --- a/src/NServiceBus.AcceptanceTests/Msmq/When_unsubscribing_with_authorizer.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Msmq -{ - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.PubSub; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_unsubscribing_with_authorizer : NServiceBusAcceptanceTest - { - - [Test] - public void Should_ignore_unsubscribe() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscribed, (bus, c) => - { - bus.Publish(new MyEvent()); - }).When(c => c.SubscriberEventCount ==1 , (bus, c) => - { - bus.Publish(new MyEvent()); - }) - ) - .WithEndpoint(b => b.When(c => c.PublisherStarted, bus => - { - bus.Subscribe(); - })) - .Done(c => - c.SubscriberEventCount == 2 && - c.DeclinedUnSubscribe) - .Repeat(r => r.For(Transports.Msmq)) - .Run(); - } - - public class TestContext : ScenarioContext - { - public int SubscriberEventCount { get; set; } - public bool UnsubscribeAttempted { get; set; } - public bool DeclinedUnSubscribe { get; set; } - public bool Subscribed { get; set; } - public bool PublisherStarted { get; set; } - } - - class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => - { - b.OnEndpointSubscribed((s, context) => - { - context.Subscribed = true; - context.UnsubscribeAttempted = true; - }); - b.DisableFeature(); - }); - } - - public class CaptureStarted : IWantToRunWhenBusStartsAndStops - { - TestContext context; - - public CaptureStarted(TestContext context) - { - this.context = context; - } - - public void Start() - { - context.PublisherStarted = true; - } - - public void Stop() - { - } - } - - public class SubscriptionAuthorizer : IAuthorizeSubscriptions - { - TestContext context; - - public SubscriptionAuthorizer(TestContext context) - { - this.context = context; - } - - public bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - return true; - } - - public bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - context.DeclinedUnSubscribe = true; - return false; - } - } - } - - public class Subscriber : EndpointConfigurationBuilder - { - public Subscriber() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - IBus bus; - TestContext context; - - public MyEventHandler(IBus bus, TestContext context) - { - this.bus = bus; - this.context = context; - } - - public void Handle(MyEvent message) - { - context.SubscriberEventCount ++; - bus.Unsubscribe(); - } - } - } - - public class MyEvent : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Mutators/Issue_1980.cs b/src/NServiceBus.AcceptanceTests/Mutators/Issue_1980.cs index a41c92f90b8..2dbaebb8aa4 100644 --- a/src/NServiceBus.AcceptanceTests/Mutators/Issue_1980.cs +++ b/src/NServiceBus.AcceptanceTests/Mutators/Issue_1980.cs @@ -1,22 +1,21 @@ namespace NServiceBus.AcceptanceTests.Mutators { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; using NUnit.Framework; public class Issue_1980 : NServiceBusAcceptanceTest { [Test] - public void Run() + public async Task Run() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new V1Message()))) - .Done(c => c.V2MessageReceived) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new V1Message()))) + .Done(c => c.V2MessageReceived || c.V1MessageReceived) + .Run(); Assert.IsTrue(context.V2MessageReceived); Assert.IsFalse(context.V1MessageReceived); @@ -38,35 +37,47 @@ public Endpoint() class MutateIncomingMessages : IMutateIncomingMessages { - public object MutateIncoming(object message) + public Task MutateIncoming(MutateIncomingMessageContext message) { - if (message is V1Message) + if (message.Message is V1Message) { - return new V2Message(); + message.Message = new V2Message(); } - - return message; + return Task.FromResult(0); } } class V2MessageHandler : IHandleMessages { - public Context Context { get; set; } + public V2MessageHandler(Context testContext) + { + this.testContext = testContext; + } - public void Handle(V2Message message) + public Task Handle(V2Message message, IMessageHandlerContext context) { - Context.V2MessageReceived = true; + testContext.V2MessageReceived = true; + return Task.FromResult(0); } + + Context testContext; } class V1MessageHandler : IHandleMessages { - public Context Context { get; set; } + public V1MessageHandler(Context testContext) + { + this.testContext = testContext; + } - public void Handle(V1Message message) + public Task Handle(V1Message message, IMessageHandlerContext context) { - Context.V1MessageReceived = true; + testContext.V1MessageReceived = true; + + return Task.FromResult(0); } + + Context testContext; } } diff --git a/src/NServiceBus.AcceptanceTests/Mutators/When_defining_outgoing_message_mutators.cs b/src/NServiceBus.AcceptanceTests/Mutators/When_defining_outgoing_message_mutators.cs index 3186299bf02..d3524e14739 100644 --- a/src/NServiceBus.AcceptanceTests/Mutators/When_defining_outgoing_message_mutators.cs +++ b/src/NServiceBus.AcceptanceTests/Mutators/When_defining_outgoing_message_mutators.cs @@ -1,26 +1,23 @@ namespace NServiceBus.AcceptanceTests.Mutators { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; - using NServiceBus.Unicast.Messages; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; using NUnit.Framework; public class When_defining_outgoing_message_mutators : NServiceBusAcceptanceTest { [Test] - public void Should_be_applied_to_outgoing_messages() + public async Task Should_be_applied_to_outgoing_messages() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeMutated()))) - .Done(c => c.MessageProcessed) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new Message()))) + .Done(c => c.MessageProcessed) + .Run(); Assert.True(context.TransportMutatorCalled); - Assert.IsTrue(context.OutgoingMessageLogicalMessageReceived); Assert.True(context.MessageMutatorCalled); } @@ -29,73 +26,73 @@ public class Context : ScenarioContext public bool MessageProcessed { get; set; } public bool TransportMutatorCalled { get; set; } public bool MessageMutatorCalled { get; set; } - public bool OutgoingMessageLogicalMessageReceived { get; set; } } - public class OutgoingMutatorEndpoint : EndpointConfigurationBuilder + public class Endpoint : EndpointConfigurationBuilder { - public OutgoingMutatorEndpoint() + public Endpoint() { - EndpointSetup(); + EndpointSetup(c => c.RegisterComponents( + components => + { + components.ConfigureComponent(DependencyLifecycle.InstancePerCall); + components.ConfigureComponent(DependencyLifecycle.InstancePerCall); + })); } - - class MyTransportMessageMutator : IMutateOutgoingTransportMessages, INeedInitialization + class TransportMutator : + IMutateOutgoingTransportMessages { - - public Context Context { get; set; } - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) + public TransportMutator(Context testContext) { - Context.OutgoingMessageLogicalMessageReceived = logicalMessage != null; - transportMessage.Headers["TransportMutatorCalled"] = true.ToString(); + this.testContext = testContext; } - public void Customize(BusConfiguration configuration) + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + testContext.TransportMutatorCalled = true; + return Task.FromResult(0); } + + Context testContext; } - class MyMessageMutator : IMutateOutgoingMessages, INeedInitialization + class MessageMutator : IMutateOutgoingMessages { - public IBus Bus { get; set; } - - public object MutateOutgoing(object message) + public MessageMutator(Context testContext) { - Bus.SetMessageHeader(message, "MessageMutatorCalled", "true"); - - return message; + this.testContext = testContext; } - public void Customize(BusConfiguration configuration) + public Task MutateOutgoing(MutateOutgoingMessageContext context) { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + testContext.MessageMutatorCalled = true; + return Task.FromResult(0); } + Context testContext; } - class MessageToBeMutatedHandler : IHandleMessages + class Handler : IHandleMessages { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - - public void Handle(MessageToBeMutated message) + public Handler(Context testContext) { - Context.TransportMutatorCalled = Bus.CurrentMessageContext.Headers.ContainsKey("TransportMutatorCalled"); - Context.MessageMutatorCalled = Bus.CurrentMessageContext.Headers.ContainsKey("MessageMutatorCalled"); + this.testContext = testContext; + } - Context.MessageProcessed = true; + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.MessageProcessed = true; + return Task.FromResult(0); } - } + Context testContext; + } } [Serializable] - public class MessageToBeMutated : ICommand + public class Message : ICommand { } } diff --git a/src/NServiceBus.AcceptanceTests/Mutators/When_incoming_mutator_changes_message_type.cs b/src/NServiceBus.AcceptanceTests/Mutators/When_incoming_mutator_changes_message_type.cs new file mode 100644 index 00000000000..cfab0629773 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Mutators/When_incoming_mutator_changes_message_type.cs @@ -0,0 +1,129 @@ +namespace NServiceBus.AcceptanceTests.Mutators +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_incoming_mutator_changes_message_type : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_invoke_handlers_for_new_message_type() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(s => s.SendLocal(new OriginalMessage { SomeId = "TestId" }))) + .Done(c => c.NewMessageHandlerCalled || c.OriginalMessageHandlerCalled) + .Run(); + + Assert.IsTrue(context.NewMessageHandlerCalled); + Assert.IsTrue(context.NewMessageSagaHandlerCalled); + Assert.IsFalse(context.OriginalMessageHandlerCalled); + Assert.IsFalse(context.OriginalMessageSagaHandlerCalled); + } + + public class Context : ScenarioContext + { + public bool OriginalMessageHandlerCalled { get; set; } + public bool NewMessageHandlerCalled { get; set; } + public bool OriginalMessageSagaHandlerCalled { get; set; } + public bool NewMessageSagaHandlerCalled { get; set; } + } + + public class MutatorEndpoint : EndpointConfigurationBuilder + { + public MutatorEndpoint() + { + EndpointSetup(e => e + .RegisterComponents(c => c + .ConfigureComponent(DependencyLifecycle.SingleInstance))); + } + + public class MessageMutator : IMutateIncomingMessages + { + public Task MutateIncoming(MutateIncomingMessageContext context) + { + var original = (OriginalMessage)context.Message; + context.Message = new NewMessage { SomeId = original.SomeId }; + return Task.FromResult(0); + } + } + + public class OriginalMessageHandler : IHandleMessages + { + public OriginalMessageHandler(Context testContext) + { + TestContext = testContext; + } + + public Task Handle(OriginalMessage message, IMessageHandlerContext context) + { + TestContext.OriginalMessageHandlerCalled = true; + return Task.FromResult(0); + } + + Context TestContext; + } + + public class NewMessageHandler : IHandleMessages + { + public NewMessageHandler(Context testContext) + { + TestContext = testContext; + } + + public Task Handle(NewMessage message, IMessageHandlerContext context) + { + TestContext.NewMessageHandlerCalled = true; + return Task.FromResult(0); + } + + Context TestContext; + } + + public class Saga : Saga, IAmStartedByMessages, IAmStartedByMessages + { + public Saga(Context testContext) + { + TestContext = testContext; + } + + public Task Handle(NewMessage message, IMessageHandlerContext context) + { + TestContext.NewMessageSagaHandlerCalled = true; + return Task.FromResult(0); + } + + public Task Handle(OriginalMessage message, IMessageHandlerContext context) + { + TestContext.OriginalMessageSagaHandlerCalled = true; + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + } + + Context TestContext; + } + + public class SagaData : ContainSagaData + { + public virtual string SomeId { get; set; } + } + } + + public class OriginalMessage : ICommand + { + public string SomeId { get; set; } + } + + public class NewMessage : ICommand + { + public string SomeId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Mutators/When_mutating.cs b/src/NServiceBus.AcceptanceTests/Mutators/When_mutating.cs new file mode 100644 index 00000000000..807ee976799 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Mutators/When_mutating.cs @@ -0,0 +1,127 @@ +namespace NServiceBus.AcceptanceTests.Basic +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_mutating : NServiceBusAcceptanceTest + { + [Test] + public async Task Context_should_be_populated() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new StartMessage()))) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(TimeSpan.FromHours(1)); + + Assert.True(context.WasCalled, "The message handler should be called"); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup() + .AddMapping(typeof(Receiver)); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup( + b => b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.InstancePerCall))); + } + + public class StartMessageHandler : IHandleMessages + { + public Task Handle(StartMessage message, IMessageHandlerContext context) + { + return context.SendLocal(new LoopMessage()); + } + } + + public class LoopMessageHandler : IHandleMessages + { + public LoopMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(LoopMessage message, IMessageHandlerContext context) + { + testContext.WasCalled = true; + return Task.FromResult(0); + } + + Context testContext; + } + + public class Mutator : + IMutateIncomingMessages, + IMutateIncomingTransportMessages, + IMutateOutgoingMessages, + IMutateOutgoingTransportMessages + { + public Task MutateIncoming(MutateIncomingMessageContext context) + { + Assert.IsNotEmpty(context.Headers); + Assert.IsNotNull(context.Message); + return Task.FromResult(0); + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + Assert.IsNotEmpty(context.Headers); + Assert.IsNotNull(context.Body); + return Task.FromResult(0); + } + + public Task MutateOutgoing(MutateOutgoingMessageContext context) + { + Assert.IsNotEmpty(context.OutgoingHeaders); + Assert.IsNotNull(context.OutgoingMessage); + IReadOnlyDictionary incomingHeaders; + context.TryGetIncomingHeaders(out incomingHeaders); + object incomingmessage; + context.TryGetIncomingMessage(out incomingmessage); + Assert.IsNotEmpty(incomingHeaders); + Assert.IsNotNull(incomingmessage); + return Task.FromResult(0); + } + + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + Assert.IsNotEmpty(context.OutgoingHeaders); + Assert.IsNotNull(context.OutgoingBody); + IReadOnlyDictionary incomingHeaders; + context.TryGetIncomingHeaders(out incomingHeaders); + object incomingmessage; + context.TryGetIncomingMessage(out incomingmessage); + Assert.IsNotEmpty(incomingHeaders); + Assert.IsNotNull(incomingmessage); + return Task.FromResult(0); + } + } + } + + public class StartMessage : IMessage + { + } + + public class LoopMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Mutators/When_outgoing_mutator_replaces_instance.cs b/src/NServiceBus.AcceptanceTests/Mutators/When_outgoing_mutator_replaces_instance.cs index 0083c050ddb..a28d61ac48d 100644 --- a/src/NServiceBus.AcceptanceTests/Mutators/When_outgoing_mutator_replaces_instance.cs +++ b/src/NServiceBus.AcceptanceTests/Mutators/When_outgoing_mutator_replaces_instance.cs @@ -1,20 +1,19 @@ namespace NServiceBus.AcceptanceTests.Mutators { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.MessageMutator; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; using NUnit.Framework; public class When_outgoing_mutator_replaces_instance : NServiceBusAcceptanceTest { [Test] - public void Message_sent_should_be_new_instance() + public async Task Message_sent_should_be_new_instance() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new V1Message()))) + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new V1Message()))) .Done(c => c.V2MessageReceived) .Run(); @@ -38,35 +37,48 @@ public Endpoint() class MutateOutgoingMessages : IMutateOutgoingMessages { - public object MutateOutgoing(object message) + public Task MutateOutgoing(MutateOutgoingMessageContext context) { - if (message is V1Message) + if (context.OutgoingMessage is V1Message) { - return new V2Message(); + context.OutgoingMessage = new V2Message(); } - - return message; + return Task.FromResult(0); } } - class V2MessageHandler : IHandleMessages + class V2Handler : IHandleMessages { - public Context Context { get; set; } + public V2Handler(Context testContext) + { + this.testContext = testContext; + } - public void Handle(V2Message message) + public Task Handle(V2Message message, IMessageHandlerContext context) { - Context.V2MessageReceived = true; + testContext.V2MessageReceived = true; + + return Task.FromResult(0); } + + Context testContext; } - class V1MessageHandler : IHandleMessages + class V1Handler : IHandleMessages { - public Context Context { get; set; } + public V1Handler(Context testContext) + { + this.testContext = testContext; + } - public void Handle(V1Message message) + public Task Handle(V1Message message, IMessageHandlerContext context) { - Context.V1MessageReceived = true; + testContext.V1MessageReceived = true; + + return Task.FromResult(0); } + + Context testContext; } } diff --git a/src/NServiceBus.AcceptanceTests/Mutators/When_using_outgoing_tm_mutator.cs b/src/NServiceBus.AcceptanceTests/Mutators/When_using_outgoing_tm_mutator.cs new file mode 100644 index 00000000000..fa7d1925b2e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Mutators/When_using_outgoing_tm_mutator.cs @@ -0,0 +1,86 @@ +namespace NServiceBus.AcceptanceTests.Mutators +{ + using System.Text; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_using_outgoing_tm_mutator : NServiceBusAcceptanceTest + { + [Test] + public Task Should_be_able_to_update_message() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeMutated()))) + .Done(c => c.MessageProcessed) + .Repeat(r => r.For(Serializers.Xml)) + .Should(c => + { + Assert.True(c.CanAddHeaders); + Assert.AreEqual("SomeValue", c.MutatedPropertyValue, "Mutator should be able to mutate body."); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool MessageProcessed { get; set; } + public bool CanAddHeaders { get; set; } + public string MutatedPropertyValue { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + class MyTransportMessageMutator : IMutateOutgoingTransportMessages, INeedInitialization + { + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + context.OutgoingHeaders["HeaderSetByMutator"] = "some value"; + context.OutgoingHeaders[Headers.EnclosedMessageTypes] = typeof(MessageThatMutatorChangesTo).FullName; + context.OutgoingBody = Encoding.UTF8.GetBytes("SomeValue"); + return Task.FromResult(0); + } + + public void Customize(EndpointConfiguration configuration) + { + configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + } + } + + class MessageToBeMutatedHandler : IHandleMessages + { + public MessageToBeMutatedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageThatMutatorChangesTo message, IMessageHandlerContext context) + { + testContext.CanAddHeaders = context.MessageHeaders.ContainsKey("HeaderSetByMutator"); + testContext.MutatedPropertyValue = message.SomeProperty; + testContext.MessageProcessed = true; + return Task.FromResult(0); + } + + Context testContext; + } + } + + public class MessageToBeMutated : ICommand + { + } + + public class MessageThatMutatorChangesTo : ICommand + { + public string SomeProperty { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj b/src/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj index d364c011279..560da4242ae 100644 --- a/src/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj +++ b/src/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,7 +9,7 @@ Properties NServiceBus.AcceptanceTests NServiceBus.AcceptanceTests - v4.5 + v4.5.2 512 ..\ @@ -40,113 +40,226 @@ - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True - - ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll - - - ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll - True - - - ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll - - - ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + - - - - - - - - - - + - + - - - - - - - - - - - - - - + + + + + + + - + + + + + + - - - - Code - - @@ -154,49 +267,39 @@ - - - + + + - - - + - + - - - - + + - - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + - + @@ -208,26 +311,28 @@ - + - - - + + + + nservicebus.acceptancetests.nuspec + @@ -240,14 +345,18 @@ NServiceBus.AcceptanceTesting + + + + - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - - + + \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/NServiceBusAcceptanceTest.cs b/src/NServiceBus.AcceptanceTests/NServiceBusAcceptanceTest.cs index 543d1ec3920..37b4f22a8a5 100644 --- a/src/NServiceBus.AcceptanceTests/NServiceBusAcceptanceTest.cs +++ b/src/NServiceBus.AcceptanceTests/NServiceBusAcceptanceTest.cs @@ -1,8 +1,10 @@ namespace NServiceBus.AcceptanceTests { using System.Linq; + using System.Threading; using AcceptanceTesting.Customization; using NUnit.Framework; + using ScenarioDescriptors; /// /// Base class for all the NSB test that sets up our conventions @@ -24,17 +26,14 @@ public void SetUp() var endpointBuilder = classAndEndpoint.Split('+').Last(); - - testName = System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(testName); - - testName = testName.Replace("_", ""); - + testName = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(testName); + testName = testName.Replace("_", ""); - return testName +"."+ endpointBuilder; + return testName + "." + endpointBuilder; }; - Conventions.DefaultRunDescriptor = () => ScenarioDescriptors.Transports.Default; + Conventions.DefaultRunDescriptor = () => Transports.Default; } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_blowing_up_just_after_dispatch.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_blowing_up_just_after_dispatch.cs deleted file mode 100644 index 89af76acfc1..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_blowing_up_just_after_dispatch.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - - public class When_blowing_up_just_after_dispatch : NServiceBusAcceptanceTest - { - [Test] - public void Should_still_release_the_outgoing_messages_to_the_transport() - { - - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new PlaceOrder()))) - .AllowExceptions() - .Done(c => c.OrderAckReceived == 1) - .Repeat(r=>r.For()) - .Should(context => Assert.AreEqual(1, context.OrderAckReceived, "Order ack should have been received since outbox dispatch isn't part of the receive tx")) - .Run(TimeSpan.FromSeconds(20)); - } - - - - public class Context : ScenarioContext - { - public int OrderAckReceived { get; set; } - } - - public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder - { - public NonDtcReceivingEndpoint() - { - EndpointSetup( - b => - { - b.GetSettings().Set("DisableOutboxTransportCheck", true); - b.EnableOutbox(); - b.Pipeline.Register(); - b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - }); - } - - class PlaceOrderHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(PlaceOrder message) - { - Bus.SendLocal(new SendOrderAcknowledgement()); - } - } - - class SendOrderAcknowledgementHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(SendOrderAcknowledgement message) - { - Context.OrderAckReceived++; - } - } - } - - - [Serializable] - public class PlaceOrder : ICommand { } - - [Serializable] - class SendOrderAcknowledgement : IMessage { } - } - - public class BlowUpAfterDispatchBehavior : IBehavior - { - public class Registration : RegisterStep - { - public Registration() : base("BlowUpAfterDispatchBehavior", typeof(BlowUpAfterDispatchBehavior), "For testing") - { - InsertBefore("OutboxDeduplication"); - } - } - - public void Invoke(IncomingContext context, Action next) - { - if (!context.PhysicalMessage.Headers[Headers.EnclosedMessageTypes].Contains(typeof(When_blowing_up_just_after_dispatch.PlaceOrder).Name)) - { - next(); - return; - } - - - if (called) - { - Console.Out.WriteLine("Called once, skipping next"); - return; - - } - else - { - next(); - } - - - called = true; - - throw new Exception("Fake ex after dispatch"); - } - - static bool called; - } -} diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_dispatching_deferred_message_fails_without_dtc.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_dispatching_deferred_message_fails_without_dtc.cs deleted file mode 100644 index 52a51cfbf34..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_dispatching_deferred_message_fails_without_dtc.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NServiceBus.ObjectBuilder; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NUnit.Framework; - - public class When_dispatching_deferred_message_fails_without_dtc : NServiceBusAcceptanceTest - { - [Test] - public void Message_should_be_received() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => - { - bus.Defer(TimeSpan.FromSeconds(3), new MyMessage()); - })) - .AllowExceptions() - .Done(c => c.MessageReceivedByHandler) - .Run(); - - Assert.IsTrue(context.SendingMessageFailedOnce, "Sending message attempt should fail once."); - Assert.IsTrue(context.MessageReceivedByHandler, "Message should be sent and received by handler on second attempt."); - } - - public class Context : ScenarioContext - { - public bool MessageReceivedByHandler { get; set; } - public bool SendingMessageFailedOnce { get; set; } - } - - public class TimeoutHandlingEndpoint : EndpointConfigurationBuilder - { - public TimeoutHandlingEndpoint() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.Transactions().DisableDistributedTransactions(); - }); - } - - public class DelayedMessageHandler : IHandleMessages - { - Context context; - - public DelayedMessageHandler(Context context) - { - this.context = context; - } - - public void Handle(MyMessage message) - { - context.MessageReceivedByHandler = true; - } - } - - public class EndpointConfiguration : IWantToRunBeforeConfigurationIsFinalized - { - public static IBuilder builder; - - public void Run(Configure config) - { - builder = config.Builder; - } - } - - public class DispatcherInterceptor : Feature - { - public DispatcherInterceptor() - { - EnableByDefault(); - DependsOn(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - var originalDispatcher = EndpointConfiguration.builder.Build(); - var ctx = EndpointConfiguration.builder.Build(); - context.Container.ConfigureComponent(() => new SenderWrapper(originalDispatcher, ctx), DependencyLifecycle.SingleInstance); - } - } - - class SenderWrapper : ISendMessages - { - ISendMessages wrappedSender; - Context context; - - public SenderWrapper(ISendMessages wrappedSender, Context context) - { - this.wrappedSender = wrappedSender; - this.context = context; - } - - public void Send(TransportMessage message, SendOptions sendOptions) - { - string relatedTimeoutId; - if (message.Headers.TryGetValue("NServiceBus.RelatedToTimeoutId", out relatedTimeoutId) && !context.SendingMessageFailedOnce) - { - context.SendingMessageFailedOnce = true; - throw new Exception("simulated exception"); - } - - wrappedSender.Send(message, sendOptions); - } - } - } - - [Serializable] - public class MyMessage : IMessage { } - } -} diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_outbox_with_auditing.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_outbox_with_auditing.cs deleted file mode 100644 index 90919363e69..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_outbox_with_auditing.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Configuration.AdvanceExtensibility; - using NUnit.Framework; - - public class When_outbox_with_auditing : NServiceBusAcceptanceTest - { - [Test] - public void Should_be_forwarded_to_auditQueue() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete && context.IsMessageHandledByTheAuditEndpoint) - .Repeat(r => r.For()) - .Run(); - - Assert.IsTrue(context.IsMessageHandledByTheAuditEndpoint); - } - - public class Context : ScenarioContext - { - public bool IsMessageHandlingComplete { get; set; } - public bool IsMessageHandledByTheAuditEndpoint { get; set; } - } - - public class EndpointWithOutboxAndAuditOn : EndpointConfigurationBuilder - { - - public EndpointWithOutboxAndAuditOn() - { - EndpointSetup( - b => - { - b.GetSettings().Set("DisableOutboxTransportCheck", true); - b.EnableOutbox(); - }) - .AuditTo(); - } - - class MessageToBeAuditedHandler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageToBeAudited message) - { - MyContext.IsMessageHandlingComplete = true; - } - } - } - - public class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder - { - - public EndpointThatHandlesAuditMessages() - { - EndpointSetup(); - } - - class AuditMessageHandler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageToBeAudited message) - { - MyContext.IsMessageHandledByTheAuditEndpoint = true; - } - } - } - - [Serializable] - public class MessageToBeAudited : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_receiving_a_message.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_receiving_a_message.cs deleted file mode 100644 index 0660e3c4370..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_receiving_a_message.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Configuration.AdvanceExtensibility; - using NUnit.Framework; - - public class When_receiving_a_message : NServiceBusAcceptanceTest - { - [Test] - public void Should_handle_it() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new PlaceOrder()))) - .AllowExceptions() - .Done(c => c.OrderAckReceived == 1) - .Repeat(r => r.For()) - .Run(new RunSettings { UseSeparateAppDomains = true, TestExecutionTimeout = TimeSpan.FromSeconds(20) }); - } - - [Test] - public void Should_discard_duplicates_using_the_outbox() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => - { - var duplicateMessageId = Guid.NewGuid().ToString(); - bus.SendLocal(m => bus.SetMessageHeader(m, Headers.MessageId, duplicateMessageId)); - bus.SendLocal(m => bus.SetMessageHeader(m, Headers.MessageId, duplicateMessageId)); - bus.SendLocal(new PlaceOrder()); - })) - .AllowExceptions() - .Done(c => c.OrderAckReceived >= 2) - .Repeat(r => r.For()) - .Should(context => Assert.AreEqual(2, context.OrderAckReceived)) - .Run(new RunSettings { UseSeparateAppDomains = true, TestExecutionTimeout = TimeSpan.FromSeconds(20) }); - } - - public class Context : ScenarioContext - { - public int OrderAckReceived { get; set; } - } - - public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder - { - public NonDtcReceivingEndpoint() - { - EndpointSetup( - - b => - { - b.GetSettings().Set("DisableOutboxTransportCheck", true); - b.EnableOutbox(); - }) - .AuditTo(Address.Parse("audit")); - } - - class PlaceOrderHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(PlaceOrder message) - { - Bus.SendLocal(new SendOrderAcknowledgement()); - } - } - - class SendOrderAcknowledgementHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(SendOrderAcknowledgement message) - { - Context.OrderAckReceived++; - } - } - } - - - [Serializable] - class PlaceOrder : ICommand { } - - [Serializable] - class SendOrderAcknowledgement : IMessage { } - } -} diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_a_message_with_a_ttbr.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_a_message_with_a_ttbr.cs deleted file mode 100644 index f27bd8f173d..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_a_message_with_a_ttbr.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Features; - using NServiceBus.ObjectBuilder; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NUnit.Framework; - - public class When_sending_a_message_with_a_ttbr : NServiceBusAcceptanceTest - { - [Test] - public void Should_honor_it() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new StartMessage()))) - .Done(c => c.WasCalled) - .Run(); - - Assert.AreEqual(TimeSpan.Parse("00:00:10"), context.TTBRUsed); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - public TimeSpan TTBRUsed { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup( - b => - { - b.GetSettings().Set("DisableOutboxTransportCheck", true); - b.EnableOutbox(); - }); - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - - public class StartMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(StartMessage message) - { - Bus.SendLocal(new MyMessage()) -; - } - } - - public class DispatcherInterceptor : Feature - { - public DispatcherInterceptor() - { - EnableByDefault(); - DependsOn(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - var originalDispatcher = EndpointConfiguration.builder.Build(); - var ctx = EndpointConfiguration.builder.Build(); - context.Container.ConfigureComponent(() => new SenderWrapper(originalDispatcher, ctx), DependencyLifecycle.SingleInstance); - } - } - - public class EndpointConfiguration : IWantToRunBeforeConfigurationIsFinalized - { - public static IBuilder builder; - - public void Run(Configure config) - { - builder = config.Builder; - } - } - - - class SenderWrapper : ISendMessages - { - public SenderWrapper(ISendMessages wrappedSender, Context context) - { - this.wrappedSender = wrappedSender; - this.context = context; - } - - public void Send(TransportMessage message, SendOptions sendOptions) - { - if (message.Headers[Headers.EnclosedMessageTypes].Contains("MyMessage")) - { - context.TTBRUsed = message.TimeToBeReceived; - } - - wrappedSender.Send(message, sendOptions); - } - - Context context; - ISendMessages wrappedSender; - } - } - - [TimeToBeReceived("00:00:10")] - public class MyMessage : IMessage - { - } - - public class StartMessage : IMessage - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_from_a_non_dtc_endpoint.cs b/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_from_a_non_dtc_endpoint.cs deleted file mode 100644 index e5f447fa575..00000000000 --- a/src/NServiceBus.AcceptanceTests/NonDTC/When_sending_from_a_non_dtc_endpoint.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace NServiceBus.AcceptanceTests.NonDTC -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Configuration.AdvanceExtensibility; - using NUnit.Framework; - - public class When_sending_from_a_non_dtc_endpoint : NServiceBusAcceptanceTest - { - [Test] - public void Should_store_them_and_dispatch_them_from_the_outbox() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new PlaceOrder()))) - .Done(c => c.OrderAckReceived) - .Repeat(r => r.For()) - .Should(context => Assert.IsTrue(context.OrderAckReceived)) - .Run(TimeSpan.FromSeconds(20)); - } - - public class Context : ScenarioContext - { - public bool OrderAckReceived { get; set; } - } - - public class NonDtcSalesEndpoint : EndpointConfigurationBuilder - { - public NonDtcSalesEndpoint() - { - EndpointSetup( - b => - { - b.GetSettings().Set("DisableOutboxTransportCheck", true); - b.EnableOutbox(); - }); - } - - class PlaceOrderHandler : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(PlaceOrder message) - { - Bus.SendLocal(new SendOrderAcknowledgement()); - } - } - - class SendOrderAcknowledgementHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(SendOrderAcknowledgement message) - { - Context.OrderAckReceived = true; - } - } - } - - [Serializable] - class PlaceOrder : ICommand { } - - [Serializable] - class SendOrderAcknowledgement : IMessage { } - } -} diff --git a/src/NServiceBus.AcceptanceTests/NonTx/When_sending_inside_ambient_tx.cs b/src/NServiceBus.AcceptanceTests/NonTx/When_sending_inside_ambient_tx.cs index 1acaa389aa9..b431c446d15 100644 --- a/src/NServiceBus.AcceptanceTests/NonTx/When_sending_inside_ambient_tx.cs +++ b/src/NServiceBus.AcceptanceTests/NonTx/When_sending_inside_ambient_tx.cs @@ -1,25 +1,27 @@ namespace NServiceBus.AcceptanceTests.NonTx { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; using NUnit.Framework; + using ScenarioDescriptors; public class When_sending_inside_ambient_tx : NServiceBusAcceptanceTest { [Test] - public void Should_not_roll_the_message_back_to_the_queue_in_case_of_failure() + public Task Should_not_roll_the_message_back_to_the_queue_in_case_of_failure() { - - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .AllowExceptions() - .Done(c => c.TestComplete) - .Repeat(r => r.For()) - .Should(c => Assert.False(c.MessageEnlistedInTheAmbientTxReceived, "The enlisted bus.Send should not commit")) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MyMessage())) + .DoNotFailOnErrorMessages()) + .Done(c => c.TestComplete) + .Repeat(r => r.For()) + .Should(c => Assert.False(c.MessageEnlistedInTheAmbientTxReceived, "The enlisted session.Send should not commit")) + .Run(); } public class Context : ScenarioContext @@ -33,27 +35,42 @@ public class NonTransactionalEndpoint : EndpointConfigurationBuilder { public NonTransactionalEndpoint() { - EndpointSetup(c => c.Transactions().Disable().WrapHandlersExecutionInATransactionScope()); + EndpointSetup((config, context) => + { + config.UseTransport(context.GetTransportType()).Transactions(TransportTransactionMode.None); + config.Pipeline.Register("WrapInScope", new WrapHandlersInScope(), "Wraps the handlers in a scope"); + }); + } + + class WrapHandlersInScope : IBehavior + { + public async Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + await next(context).ConfigureAwait(false); + tx.Complete(); + } + } } public class MyMessageHandler : IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public IBus Bus { get; set; } - public void Handle(MyMessage message) + public async Task Handle(MyMessage message, IMessageHandlerContext context) { - Bus.SendLocal(new CompleteTest - { - EnlistedInTheAmbientTx = true - }); + await context.SendLocal(new CompleteTest + { + EnlistedInTheAmbientTx = true + }); - using (new TransactionScope(TransactionScopeOption.Suppress)) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - Bus.SendLocal(new CompleteTest()); + await context.SendLocal(new CompleteTest()); } - throw new Exception("Simulated exception"); + throw new SimulatedException(); } } @@ -61,12 +78,16 @@ public class CompleteTestHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(CompleteTest message) + public Task Handle(CompleteTest message, IMessageHandlerContext context) { if (!Context.MessageEnlistedInTheAmbientTxReceived) + { Context.MessageEnlistedInTheAmbientTxReceived = message.EnlistedInTheAmbientTx; + } Context.TestComplete = true; + + return Task.FromResult(0); } } } @@ -81,7 +102,5 @@ public class CompleteTest : ICommand { public bool EnlistedInTheAmbientTx { get; set; } } - - } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_CriticalTime_enabled.cs b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_CriticalTime_enabled.cs index c94eb15a265..ce485cfde98 100644 --- a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_CriticalTime_enabled.cs +++ b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_CriticalTime_enabled.cs @@ -2,29 +2,29 @@ { using System.Diagnostics; using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_CriticalTime_enabled : NServiceBusAcceptanceTest { - float counterValue; - [Test] [Explicit("Since perf counters need to be enabled with powershell")] - public void Should_have_perf_counter_set() + public async Task Should_have_perf_counter_set() { using (var counter = new PerformanceCounter("NServiceBus", "Critical Time", "CriticaltimeEnabled.Endpoint", false)) - using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + { + await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) + .Run(); + } } Assert.Greater(counterValue, 0); } @@ -38,6 +38,8 @@ void CheckPerfCounter(PerformanceCounter counter) } } + float counterValue; + public class Context : ScenarioContext { public bool WasCalled { get; set; } @@ -58,10 +60,11 @@ public class MyMessage : IMessage public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - - public void Handle(MyMessage message) + + public Task Handle(MyMessage message, IMessageHandlerContext context) { Context.WasCalled = true; + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_deferring_a_message.cs b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_deferring_a_message.cs index fcc33db49bd..8c475505aba 100644 --- a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_deferring_a_message.cs +++ b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_deferring_a_message.cs @@ -3,29 +3,38 @@ using System; using System.Diagnostics; using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; + using ScenarioDescriptors; public class When_deferring_a_message : NServiceBusAcceptanceTest { - float counterValue; - [Test] [Explicit("Since perf counters need to be enabled with powershell")] - public void Critical_time_should_not_include_the_time_message_was_waiting_in_the_timeout_store() + public async Task Critical_time_should_not_include_the_time_message_was_waiting_in_the_timeout_store() { using (var counter = new PerformanceCounter("NServiceBus", "Critical Time", "DeferringAMessage.Endpoint", false)) - using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Defer(TimeSpan.FromSeconds(5), new MyMessage()))) - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + { + await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => + { + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromMilliseconds(1)); + options.RouteToThisEndpoint(); + + return session.Send(new MyMessage(), options); + })) + .Done(c => c.WasCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) + .Run(); + } } Assert.Greater(counterValue, 0, "Critical time has not been recorded"); Assert.Less(counterValue, 2); @@ -40,6 +49,8 @@ void CheckPerfCounter(PerformanceCounter counter) } } + float counterValue; + public class Context : ScenarioContext { public bool WasCalled { get; set; } @@ -49,7 +60,11 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(builder => builder.EnableCriticalTimePerformanceCounter()) + EndpointSetup(builder => + { + builder.EnableCriticalTimePerformanceCounter(); + builder.EnableFeature(); + }) .AddMapping(typeof(Endpoint)); } } @@ -61,10 +76,12 @@ public class MyMessage : IMessage public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - - public void Handle(MyMessage message) + + public Task Handle(MyMessage message, IMessageHandlerContext context) { Context.WasCalled = true; + + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_slow_with_CriticalTime_enabled.cs b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_slow_with_CriticalTime_enabled.cs index 2031cbe872d..cc3939f4e5a 100644 --- a/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_slow_with_CriticalTime_enabled.cs +++ b/src/NServiceBus.AcceptanceTests/PerfMon/CriticalTime/When_slow_with_CriticalTime_enabled.cs @@ -2,29 +2,29 @@ { using System.Diagnostics; using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_slow_with_CriticalTime_enabled : NServiceBusAcceptanceTest { - float counterValue; - [Test] [Explicit("Since perf counters need to be enabled with powershell")] - public void Should_have_perf_counter_set() + public async Task Should_have_perf_counter_set() { using (var counter = new PerformanceCounter("NServiceBus", "Critical Time", "SlowWithCriticaltimeEnabled.Endpoint", true)) - using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + { + await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) + .Run(); + } } Assert.Greater(counterValue, 2); } @@ -38,6 +38,8 @@ void CheckPerfCounter(PerformanceCounter counter) } } + float counterValue; + public class Context : ScenarioContext { public bool WasCalled { get; set; } @@ -58,9 +60,10 @@ public class MyMessage : IMessage public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage message) + + public async Task Handle(MyMessage message, IMessageHandlerContext context) { - Thread.Sleep(2000); + await Task.Delay(2000); Context.WasCalled = true; } } diff --git a/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_slow_with_SLA_enabled.cs b/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_slow_with_SLA_enabled.cs index d0728334fb6..131e4eef036 100644 --- a/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_slow_with_SLA_enabled.cs +++ b/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_slow_with_SLA_enabled.cs @@ -3,29 +3,29 @@ using System; using System.Diagnostics; using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_sending_slow_with_SLA_enabled : NServiceBusAcceptanceTest { - float counterValue; - [Test] [Explicit("Since perf counters need to be enabled with powershell")] - public void Should_have_perf_counter_set() + public async Task Should_have_perf_counter_set() { - using (var counter = new PerformanceCounter("NServiceBus", "SLA violation countdown", "PerformanceMonitoring.Endpoint.WhenSendingSlowWithSLAEnabled." + Transports.Default.Key, true)) - using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + using (var counter = new PerformanceCounter("NServiceBus", "SLA violation countdown", "SendingWithSLAEnabled.Endpoint", false)) { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + { + await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) + .Run(); + } } Assert.Greater(counterValue, 2); } @@ -39,6 +39,8 @@ void CheckPerfCounter(PerformanceCounter counter) } } + float counterValue; + public class Context : ScenarioContext { public bool WasCalled { get; set; } @@ -59,10 +61,10 @@ public class MyMessage : IMessage public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - - public void Handle(MyMessage message) + + public async Task Handle(MyMessage message, IMessageHandlerContext context) { - Thread.Sleep(1000); + await Task.Delay(1000); Context.WasCalled = true; } } diff --git a/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_with_SLA_enabled.cs b/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_with_SLA_enabled.cs index 01be2560830..cd760565d1a 100644 --- a/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_with_SLA_enabled.cs +++ b/src/NServiceBus.AcceptanceTests/PerfMon/SLA/When_sending_with_SLA_enabled.cs @@ -3,29 +3,29 @@ using System; using System.Diagnostics; using System.Threading; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_sending_with_SLA_enabled : NServiceBusAcceptanceTest { - float counterValue; - [Test] [Explicit("Since perf counters need to be enabled with powershell")] - public void Should_have_perf_counter_set() + public async Task Should_have_perf_counter_set() { - using (var counter = new PerformanceCounter("NServiceBus", "SLA violation countdown", "PerformanceMonitoring.Endpoint.WhenSendingWithSLAEnabled." + Transports.Default.Key, true)) - using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + using (var counter = new PerformanceCounter("NServiceBus", "SLA violation countdown", "SendingWithSLAEnabled.Endpoint", false)) { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); + using (new Timer(state => CheckPerfCounter(counter), null, 0, 100)) + { + await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) + .Run(); + } } Assert.Greater(counterValue, 0); } @@ -39,6 +39,8 @@ void CheckPerfCounter(PerformanceCounter counter) } } + float counterValue; + public class Context : ScenarioContext { public bool WasCalled { get; set; } @@ -52,7 +54,6 @@ public Endpoint() } } - public class MyMessage : IMessage { } @@ -61,9 +62,10 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage message) + public Task Handle(MyMessage message, IMessageHandlerContext context) { Context.WasCalled = true; + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired.cs b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired.cs new file mode 100644 index 00000000000..c05f1f57e2b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired.cs @@ -0,0 +1,73 @@ +namespace NServiceBus.AcceptanceTests.Performance.TimeToBeReceived +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_TimeToBeReceived_has_expired : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_not_be_received() + { + var context = await Scenario.Define() + .WithEndpoint() + .Run(TimeSpan.FromSeconds(10)); + + Assert.IsFalse(context.WasCalled); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + class DelayReceiverFromStarting : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new DelayReceiverFromStartingTask()); + } + } + + class DelayReceiverFromStartingTask : FeatureStartupTask + { + protected override async Task OnStart(IMessageSession session) + { + await session.SendLocal(new MyMessage()); + await Task.Delay(TimeSpan.FromSeconds(5)); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => c.EnableFeature()); + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + TestContext.WasCalled = true; + return Task.FromResult(0); + } + } + } + + [TimeToBeReceived("00:00:02")] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired_convention.cs b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired_convention.cs new file mode 100644 index 00000000000..13e4c2f5414 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_expired_convention.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.Performance.TimeToBeReceived +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_TimeToBeReceived_has_expired_convention : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_not_be_received() + { + var context = await Scenario.Define() + .WithEndpoint() + .Run(TimeSpan.FromSeconds(10)); + + Assert.IsFalse(context.WasCalled); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + class DelayReceiverFromStarting : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new DelayReceiverFromStartingTask()); + } + } + + class DelayReceiverFromStartingTask : FeatureStartupTask + { + protected override async Task OnStart(IMessageSession session) + { + await session.SendLocal(new MyMessage()); + await Task.Delay(TimeSpan.FromSeconds(5)); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.EnableFeature(); + c.Conventions().DefiningTimeToBeReceivedAs(messageType => + { + if (messageType == typeof(MyMessage)) + { + return TimeSpan.FromSeconds(2); + } + return TimeSpan.MaxValue; + }); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_not_expired.cs b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_not_expired.cs new file mode 100644 index 00000000000..06ec1a4beea --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_has_not_expired.cs @@ -0,0 +1,55 @@ +namespace NServiceBus.AcceptanceTests.Performance.TimeToBeReceived +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_TimeToBeReceived_has_not_expired : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_be_received() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage()))) + .Done(c => c.WasCalled) + .Run(); + + Assert.IsTrue(context.WasCalled); + Assert.AreEqual(TimeSpan.FromSeconds(10), context.TTBROnIncomingMessage, "TTBR should be available as a header so receiving endpoints can know what value was used when the message was originally sent"); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public TimeSpan TTBROnIncomingMessage { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + TestContext.TTBROnIncomingMessage = TimeSpan.Parse(context.MessageHeaders[Headers.TimeToBeReceived]); + TestContext.WasCalled = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + [TimeToBeReceived("00:00:10")] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_used_with_unobtrusive_mode.cs b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_used_with_unobtrusive_mode.cs new file mode 100644 index 00000000000..eeb3286bcc6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/TimeToBeReceived/When_TimeToBeReceived_used_with_unobtrusive_mode.cs @@ -0,0 +1,119 @@ +namespace NServiceBus.AcceptanceTests.Performance.TimeToBeReceived +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_TimeToBeReceived_used_with_unobtrusive_mode : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_not_be_received() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint() + .Run(TimeSpan.FromSeconds(10)); + + Assert.IsFalse(context.WasCalled); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + } + + class SendMessageWhileStarting : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new SendMessageWhileStartingTask()); + } + } + + class SendMessageWhileStartingTask : FeatureStartupTask + { + protected override Task OnStart(IMessageSession session) + { + return session.Send(new MyCommand()); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.Conventions() + .DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyCommand).FullName) + .DefiningTimeToBeReceivedAs(messageType => + { + if (messageType == typeof(MyCommand)) + { + return TimeSpan.FromSeconds(2); + } + return TimeSpan.MaxValue; + }); + c.EnableFeature(); + }).AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + class DelayReceiverFromStarting : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new DelayReceiverFromStartingTask()); + } + } + + class DelayReceiverFromStartingTask : FeatureStartupTask + { + protected override Task OnStart(IMessageSession session) + { + return Task.Delay(TimeSpan.FromSeconds(5)); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName == typeof(MyCommand).FullName); + c.EnableFeature(); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyCommand message, IMessageHandlerContext context) + { + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + public class MyCommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Performance/When_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Performance/When_message_is_audited.cs new file mode 100644 index 00000000000..f81961dcf38 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/When_message_is_audited.cs @@ -0,0 +1,87 @@ +namespace NServiceBus.AcceptanceTests.Performance +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_message_is_audited : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_contain_processing_stats_headers() + { + var now = DateTime.UtcNow; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited()))) + .WithEndpoint() + .Done(c => c.IsMessageHandledByTheAuditEndpoint) + .Run(); + + var processingStarted = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.ProcessingStarted]); + var processingEnded = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.ProcessingEnded]); + var timeSent = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.TimeSent]); + + Assert.That(processingStarted, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(processingEnded, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(timeSent, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(processingStarted, Is.LessThanOrEqualTo(processingEnded)); + Assert.That(timeSent, Is.LessThanOrEqualTo(processingEnded)); + Assert.IsTrue(context.IsMessageHandledByTheAuditEndpoint); + } + + public class Context : ScenarioContext + { + public bool IsMessageHandledByTheAuditEndpoint { get; set; } + public IReadOnlyDictionary Headers { get; set; } + } + + public class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup() + .AuditTo(); + } + + class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context1) + { + return Task.FromResult(0); + } + } + } + + public class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesAuditMessages() + { + EndpointSetup(); + } + + class AuditMessageHandler : IHandleMessages + { + public AuditMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + testContext.Headers = context.MessageHeaders; + testContext.IsMessageHandledByTheAuditEndpoint = true; + return Task.FromResult(0); + } + + Context testContext; + } + } + + public class MessageToBeAudited : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Performance/When_message_is_faulted.cs b/src/NServiceBus.AcceptanceTests/Performance/When_message_is_faulted.cs new file mode 100644 index 00000000000..7cceb00002d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Performance/When_message_is_faulted.cs @@ -0,0 +1,137 @@ +namespace NServiceBus.AcceptanceTests.Performance +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_message_is_faulted : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_contain_processing_stats_headers() + { + var now = DateTime.UtcNow; + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MessageToBeAudited())).DoNotFailOnErrorMessages()) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.IsMessageHandledByTheAuditEndpoint && c.IsMessageHandledByTheFaultEndpoint) + .Run(); + + var processingStarted = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.ProcessingStarted]); + var processingEnded = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.ProcessingEnded]); + var timeSent = DateTimeExtensions.ToUtcDateTime(context.Headers[Headers.TimeSent]); + var timeSentWhenFailedMessageWasSentToTheErrorQueue = DateTimeExtensions.ToUtcDateTime(context.FaultHeaders[Headers.TimeSent]); + + Assert.That(processingStarted, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(processingEnded, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(timeSent, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(timeSentWhenFailedMessageWasSentToTheErrorQueue, Is.EqualTo(now).Within(TimeSpan.FromSeconds(30))); + Assert.That(timeSent, Is.LessThanOrEqualTo(processingEnded)); + Assert.That(timeSent, Is.LessThanOrEqualTo(timeSentWhenFailedMessageWasSentToTheErrorQueue)); + + Assert.That(timeSentWhenFailedMessageWasSentToTheErrorQueue, Is.EqualTo(context.TimeSentOnTheFailingMessageWhenItWasHandled)); + Assert.That(processingStarted, Is.LessThanOrEqualTo(processingEnded)); + Assert.IsTrue(context.IsMessageHandledByTheFaultEndpoint); + } + + public class Context : ScenarioContext + { + public bool IsMessageHandledByTheAuditEndpoint { get; set; } + public bool IsMessageHandledByTheFaultEndpoint { get; set; } + public IReadOnlyDictionary Headers { get; set; } + public IReadOnlyDictionary FaultHeaders { get; set; } + public DateTime TimeSentOnTheFailingMessageWhenItWasHandled { get; set; } + } + + public class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup((c, r) => { c.SendFailedMessagesTo("errorQueueForAcceptanceTest"); }).AuditTo(); + } + + class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return context.SendLocal(new MessageThatFails()); + } + + public class MessageSentInsideHandlersHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + } + + public class EndpointThatHandlesAuditMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesAuditMessages() + { + EndpointSetup(); + } + + class AuditMessageHandler : IHandleMessages + { + public AuditMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + testContext.Headers = context.MessageHeaders; + testContext.IsMessageHandledByTheAuditEndpoint = true; + return Task.FromResult(0); + } + + Context testContext; + } + } + + class EndpointThatHandlesErrorMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesErrorMessages() + { + EndpointSetup() + .CustomEndpointName("errorQueueForAcceptanceTest"); + } + + class ErrorMessageHandler : IHandleMessages + { + public ErrorMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + testContext.TimeSentOnTheFailingMessageWhenItWasHandled = DateTimeExtensions.ToUtcDateTime(context.MessageHeaders[Headers.TimeSent]); + testContext.FaultHeaders = context.MessageHeaders; + testContext.IsMessageHandledByTheFaultEndpoint = true; + + return Task.FromResult(0); + } + + Context testContext; + } + } + + public class MessageToBeAudited : IMessage + { + } + + public class MessageThatFails : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Pipeline/When_replacing_behavior.cs b/src/NServiceBus.AcceptanceTests/Pipeline/When_replacing_behavior.cs new file mode 100644 index 00000000000..320975e7da3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Pipeline/When_replacing_behavior.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.AcceptanceTests.Pipeline +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_replacing_behavior : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_invoke_replacement_in_pipeline() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(s => s.SendLocal(m => { }))) + .Done(c => c.MessageHandled) + .Run(); + + Assert.IsFalse(context.OriginalBehaviorInvoked); + Assert.IsTrue(context.ReplacementBehaviorInvoked); + } + + class Context : ScenarioContext + { + public bool OriginalBehaviorInvoked { get; set; } + public bool ReplacementBehaviorInvoked { get; set; } + + public bool MessageHandled { get; set; } + } + + class OriginalBehavior : IBehavior + { + public OriginalBehavior(Context testContext) + { + this.testContext = testContext; + } + + public Task Invoke(ITransportReceiveContext context, Func next) + { + testContext.OriginalBehaviorInvoked = true; + return next(context); + } + + Context testContext; + } + + class ReplacementBehavior : IBehavior + { + public ReplacementBehavior(Context testContext) + { + this.testContext = testContext; + } + + public Task Invoke(ITransportReceiveContext context, Func next) + { + testContext.ReplacementBehaviorInvoked = true; + return next(context); + } + + Context testContext; + } + + class EndpointWithReplacement : EndpointConfigurationBuilder + { + public EndpointWithReplacement() + { + EndpointSetup(c => + { + // replace before register to ensure out-of-order replacements work correctly. + c.Pipeline.Replace("demoBehavior", new ReplacementBehavior((Context)ScenarioContext)); + c.Pipeline.Register("demoBehavior", new OriginalBehavior((Context)ScenarioContext), "test behavior replacement"); + }); + } + + public class Handler : IHandleMessages + { + public Handler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.MessageHandled = true; + return Task.FromResult(0); + } + + Context testContext; + } + } + + class Message : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Pipeline/When_using_per_uow_component_in_the_pipeline.cs b/src/NServiceBus.AcceptanceTests/Pipeline/When_using_per_uow_component_in_the_pipeline.cs new file mode 100644 index 00000000000..1f0e18c4f23 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Pipeline/When_using_per_uow_component_in_the_pipeline.cs @@ -0,0 +1,119 @@ +namespace NServiceBus.AcceptanceTests.Pipeline +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class When_using_per_uow_component_in_the_pipeline : NServiceBusAcceptanceTest + { + [Test] + public async Task It_should_be_scoped_to_uow_both_in_behavior_and_in_the_handler() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(async s => + { + await SendMessage(s).ConfigureAwait(false); + await SendMessage(s).ConfigureAwait(false); + })) + .Done(c => c.MessagesProcessed >= 2) + .Run(); + + Assert.IsFalse(context.ValueEmpty, "Empty value in the UoW component meaning the UoW component has been registered as per-call"); + Assert.IsFalse(context.ValueAlreadyInitialized, "Value in the UoW has already been initialized when it was resolved for the first time in a given pipeline meaning the UoW component has been registered as a singleton."); + } + + static Task SendMessage(IMessageSession s) + { + var uniqueValue = Guid.NewGuid().ToString(); + var options = new SendOptions(); + options.RouteToThisEndpoint(); + options.SetHeader("Value", uniqueValue); + var message = new Message + { + Value = uniqueValue + }; + + return s.Send(message, options); + } + + class Context : ScenarioContext + { + int messagesProcessed; + public int MessagesProcessed => messagesProcessed; + + public void OnMessageProcessed() + { + Interlocked.Increment(ref messagesProcessed); + } + + public bool ValueEmpty { get; set; } + public bool ValueAlreadyInitialized { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.InstancePerUnitOfWork)); + c.Pipeline.Register(b => new HeaderProcessingBehavior(b.Build()), "Populates UoW component."); + c.LimitMessageProcessingConcurrencyTo(1); + }); + } + + class HeaderProcessingBehavior : IBehavior + { + Context testContext; + + public HeaderProcessingBehavior(Context testContext) + { + this.testContext = testContext; + } + + public Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + var uowScopeComponent = context.Builder.Build(); + testContext.ValueAlreadyInitialized |= uowScopeComponent.ValueFromHeader != null; + uowScopeComponent.ValueFromHeader = context.MessageHeaders["Value"]; + + return next(context); + } + } + + class UnitOfWorkComponent + { + public string ValueFromHeader { get; set; } + } + + class Handler : IHandleMessages + { + public Handler(Context testContext, UnitOfWorkComponent component) + { + this.testContext = testContext; + this.component = component; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.ValueEmpty |= component.ValueFromHeader == null; + testContext.OnMessageProcessed(); + return Task.FromResult(0); + } + + Context testContext; + UnitOfWorkComponent component; + } + } + + class Message : IMessage + { + public string Value { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PipelineExt/FilteringWhatGetsAudited.cs b/src/NServiceBus.AcceptanceTests/PipelineExt/FilteringWhatGetsAudited.cs deleted file mode 100644 index 8561749bef7..00000000000 --- a/src/NServiceBus.AcceptanceTests/PipelineExt/FilteringWhatGetsAudited.cs +++ /dev/null @@ -1,135 +0,0 @@ - -namespace NServiceBus.AcceptanceTests.PipelineExt -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NUnit.Framework; - - /// - /// This is a demo on how pipeline overrides can be used to control which messages that gets audited by NServiceBus - /// - public class FilteringWhatGetsAudited : NServiceBusAcceptanceTest - { - [Test] - public void RunDemo() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeAudited()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete) - .Run(); - - Assert.IsFalse(context.MessageAudited); - } - - - public class UserEndpoint : EndpointConfigurationBuilder - { - public UserEndpoint() - { - EndpointSetup() - .AuditTo(); - } - - class MessageToBeAuditedHandler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageToBeAudited message) - { - MyContext.IsMessageHandlingComplete = true; - } - } - - class SetFiltering : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - if (context.IncomingLogicalMessage.MessageType == typeof(MessageToBeAudited)) - { - context.Get().DoNotAuditMessage = true; - } - } - - class AuditFilteringOverride : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.Pipeline.Register("SetFiltering", typeof(SetFiltering), "Filters audit entries"); - } - } - } - - class AuditFilterResult - { - public bool DoNotAuditMessage { get; set; } - } - - class FilteringAuditBehavior : IBehavior - { - public IAuditMessages MessageAuditer { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var auditResult = new AuditFilterResult(); - context.Set(auditResult); - next(); - - //note: and rule operating on the raw TransportMessage can be applied here if needed. - // Access to the message is through: context.PhysicalMessage. Eg: context.PhysicalMessage.Headers.ContainsKey("NServiceBus.ControlMessage") - if (auditResult.DoNotAuditMessage) - { - return; - } - MessageAuditer.Audit(new SendOptions("audit"),context.PhysicalMessage); - } - - //here we inject our behavior - class AuditFilteringOverride : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - //we replace the default audit behavior with out own - configuration.Pipeline.Replace(WellKnownStep.AuditProcessedMessage, typeof(FilteringAuditBehavior), "A new audit forwarder that has filtering"); - } - } - } - } - - public class AuditSpy : EndpointConfigurationBuilder - { - public AuditSpy() - { - EndpointSetup(); - } - - class AuditMessageHandler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageToBeAudited message) - { - MyContext.MessageAudited = true; - } - } - } - - public class Context : ScenarioContext - { - public bool IsMessageHandlingComplete { get; set; } - public bool MessageAudited { get; set; } - } - - - [Serializable] - public class MessageToBeAudited : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/PipelineExt/MutingHandlerExceptions.cs b/src/NServiceBus.AcceptanceTests/PipelineExt/MutingHandlerExceptions.cs deleted file mode 100644 index c1757d4c80f..00000000000 --- a/src/NServiceBus.AcceptanceTests/PipelineExt/MutingHandlerExceptions.cs +++ /dev/null @@ -1,120 +0,0 @@ - -namespace NServiceBus.AcceptanceTests.PipelineExt -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - - /// - /// This is a demo on how pipeline overrides can be used to control which messages that gets audited by NServiceBus - /// - public class MutingHandlerExceptions : NServiceBusAcceptanceTest - { - [Test] - public void RunDemo() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageThatWillBlowUpButExWillBeMuted()))) - .WithEndpoint() - .Done(c => c.IsMessageHandlingComplete) - .Run(); - - Assert.IsTrue(context.MessageAudited); - } - - public class EndpointWithCustomExceptionMuting : EndpointConfigurationBuilder - { - public EndpointWithCustomExceptionMuting() - { - EndpointSetup() - .AuditTo(); - } - - class Handler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageThatWillBlowUpButExWillBeMuted message) - { - MyContext.IsMessageHandlingComplete = true; - - throw new Exception("Lets filter on this text"); - } - } - - class MyExceptionFilteringBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - try - { - //invoke the handler/rest of the pipeline - next(); - } - //catch specifix exceptions or - catch (Exception ex) - { - //modify this to your liking - if (ex.Message == "Lets filter on this text") - { - return; - } - - throw; - } - } - - //here we inject our behavior - class MyExceptionFilteringOverride : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.Pipeline.Register(); - } - } - - class MyExceptionFilteringRegistration : RegisterStep - { - public MyExceptionFilteringRegistration() : base("ExceptionFiltering", typeof(MyExceptionFilteringBehavior), "Custom exception filtering") - { - InsertAfter(WellKnownStep.AuditProcessedMessage); - InsertBefore(WellKnownStep.InvokeHandlers); - } - } - } - } - - public class AuditSpy : EndpointConfigurationBuilder - { - public AuditSpy() - { - EndpointSetup(); - } - - class AuditMessageHandler : IHandleMessages - { - public Context MyContext { get; set; } - - public void Handle(MessageThatWillBlowUpButExWillBeMuted message) - { - MyContext.MessageAudited = true; - } - } - } - - public class Context : ScenarioContext - { - public bool IsMessageHandlingComplete { get; set; } - public bool MessageAudited { get; set; } - } - - [Serializable] - public class MessageThatWillBlowUpButExWillBeMuted : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/PipelineExt/SkipDeserialization.cs b/src/NServiceBus.AcceptanceTests/PipelineExt/SkipDeserialization.cs deleted file mode 100644 index 4123b5ae594..00000000000 --- a/src/NServiceBus.AcceptanceTests/PipelineExt/SkipDeserialization.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PipelineExt -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - - //This is a demo on how the pipeline overrides can be used to create endpoints that doesn't deserialize incoming messages and there by - // allows the user to handle the raw transport message. This replaces the old feature on the UnicastBus where SkipDeserialization could be set to tru - public class SkipDeserialization : NServiceBusAcceptanceTest - { - [Test] - public void RunDemo() - { - Scenario.Define() - .WithEndpoint( - b => b.Given(bus => bus.SendLocal(new SomeMessage()))) - .Done(c => c.GotTheRawMessage) - .Run(); - } - - public class NonSerializingEndpoint : EndpointConfigurationBuilder - { - public NonSerializingEndpoint() - { - EndpointSetup(); - } - - //first we override the default "extraction" behavior - class MyOverride : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.Pipeline.Replace(WellKnownStep.DeserializeMessages, typeof(MyRawMessageHandler)); - } - } - - //and then we handle the physical message our self - class MyRawMessageHandler:IBehavior - { - public Context Context { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var transportMessage = context.PhysicalMessage; - - Assert.True(transportMessage.Headers[Headers.EnclosedMessageTypes].Contains(typeof(SomeMessage).Name)); - - Context.GotTheRawMessage = true; - } - } - - class ThisHandlerWontGetInvoked:IHandleMessages - { - public void Handle(SomeMessage message) - { - Assert.Fail(); - } - } - } - - public class Context : ScenarioContext - { - public bool GotTheRawMessage { get; set; } - } - - [Serializable] - public class SomeMessage : ICommand - { - - } - } - - -} diff --git a/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionBehavior.cs b/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionBehavior.cs deleted file mode 100644 index 4886df7cafc..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionBehavior.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using Pipeline; - using Pipeline.Contexts; - - static class SubscriptionBehaviorExtensions - { - public static void OnEndpointSubscribed(this BusConfiguration b, Action action) where TContext : ScenarioContext - { - b.Pipeline.Register.Registration>(); - - b.RegisterComponents(c => c.ConfigureComponent(builder => - { - var context = builder.Build(); - return new SubscriptionBehavior(action, context); - }, DependencyLifecycle.InstancePerCall)); - } - } - - class SubscriptionBehavior : IBehavior where TContext : ScenarioContext - { - readonly Action action; - readonly TContext scenarioContext; - - public SubscriptionBehavior(Action action, TContext scenarioContext) - { - this.action = action; - this.scenarioContext = scenarioContext; - } - - public void Invoke(IncomingContext context, Action next) - { - next(); - var subscriptionMessageType = GetSubscriptionMessageTypeFrom(context.PhysicalMessage); - if (subscriptionMessageType != null) - { - action(new SubscriptionEventArgs - { - MessageType = subscriptionMessageType, - SubscriberReturnAddress = context.PhysicalMessage.ReplyToAddress - }, scenarioContext); - } - } - - static string GetSubscriptionMessageTypeFrom(TransportMessage msg) - { - return (from header in msg.Headers where header.Key == Headers.SubscriptionMessageType select header.Value).FirstOrDefault(); - } - - internal class Registration : RegisterStep - { - public Registration() - : base("SubscriptionBehavior", typeof(SubscriptionBehavior), "So we can get subscription events") - { - InsertBefore(WellKnownStep.CreateChildContainer); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionEventArgs.cs b/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionEventArgs.cs deleted file mode 100644 index 5c73c1f054a..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/SubscriptionEventArgs.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - public class SubscriptionEventArgs - { - /// - /// The address of the subscriber. - /// - public Address SubscriberReturnAddress { get; set; } - - /// - /// The type of message the client subscribed to. - /// - public string MessageType { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_base_event_from_2_publishers.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_base_event_from_2_publishers.cs deleted file mode 100644 index 8acefcb0071..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_base_event_from_2_publishers.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - - public class When_base_event_from_2_publishers : NServiceBusAcceptanceTest - { - [Test] - public void Should_receive_events_from_all_publishers() - { - var cc = new Context(); - - Scenario.Define(cc) - .WithEndpoint(b => - b.When(c => c.SubscribedToPublisher1, bus => bus.Publish(new DerivedEvent1())) - ) - .WithEndpoint(b => - b.When(c => c.SubscribedToPublisher2, bus => bus.Publish(new DerivedEvent2())) - ) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.SubscribedToPublisher1 = true; - context.SubscribedToPublisher2 = true; - } - })) - .AllowExceptions(e => e.Message.Contains("Oracle.DataAccess.Client.OracleException: ORA-00001") || e.Message.Contains("System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint")) - .Done(c => c.GotTheEventFromPublisher1 && c.GotTheEventFromPublisher2) - .Run(); - - Assert.True(cc.GotTheEventFromPublisher1); - Assert.True(cc.GotTheEventFromPublisher2); - } - - public class Context : ScenarioContext - { - public bool GotTheEventFromPublisher1 { get; set; } - public bool GotTheEventFromPublisher2 { get; set; } - public bool SubscribedToPublisher1 { get; set; } - public bool SubscribedToPublisher2 { get; set; } - } - - public class Publisher1 : EndpointConfigurationBuilder - { - public Publisher1() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - context.AddTrace("Publisher1 SubscriberReturnAddress=" + s.SubscriberReturnAddress.Queue); - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.SubscribedToPublisher1 = true; - } - })); - } - } - - public class Publisher2 : EndpointConfigurationBuilder - { - public Publisher2() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - context.AddTrace("Publisher2 SubscriberReturnAddress=" + s.SubscriberReturnAddress.Queue); - - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.SubscribedToPublisher2 = true; - } - })); - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup() - .AddMapping(typeof(Publisher1)) - .AddMapping(typeof(Publisher2)); - } - - public class BaseEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(BaseEvent message) - { - if (message.GetType().FullName.Contains("DerivedEvent1")) - Context.GotTheEventFromPublisher1 = true; - if (message.GetType().FullName.Contains("DerivedEvent2")) - Context.GotTheEventFromPublisher2 = true; - } - } - } - - [Serializable] - public class BaseEvent : IEvent - { - } - - [Serializable] - public class DerivedEvent1 : BaseEvent - { - - } - - [Serializable] - public class DerivedEvent2 : BaseEvent - { - - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_multi_subscribing_to_a_polymorphic_event.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_multi_subscribing_to_a_polymorphic_event.cs deleted file mode 100644 index ec09dc447c8..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_multi_subscribing_to_a_polymorphic_event.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using AcceptanceTesting; - using EndpointTemplates; - using Features; - using NUnit.Framework; - - public class When_multi_subscribing_to_a_polymorphic_event : NServiceBusAcceptanceTest - { - [Test] - public void Both_events_should_be_delivered() - { - var rootContext = new Context(); - - Scenario.Define(rootContext) - .WithEndpoint(b => b.When(c => c.Publisher1HasASubscriberForIMyEvent, (bus, c) => - { - c.AddTrace("Publishing MyEvent1"); - bus.Publish(new MyEvent1()); - })) - .WithEndpoint(b => b.When(c => c.Publisher2HasDetectedASubscriberForEvent2, (bus, c) => - { - c.AddTrace("Publishing MyEvent2"); - bus.Publish(new MyEvent2()); - })) - .WithEndpoint(b => b.Given((bus, context) => - { - context.AddTrace("Subscriber1 subscribing to both events"); - bus.Subscribe(); - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.Publisher1HasASubscriberForIMyEvent = true; - context.Publisher2HasDetectedASubscriberForEvent2 = true; - } - })) - .AllowExceptions(e => e.Message.Contains("Oracle.DataAccess.Client.OracleException: ORA-00001") || e.Message.Contains("System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint")) - .Done(c => c.SubscriberGotIMyEvent && c.SubscriberGotMyEvent2) - .Run(); - - Assert.True(rootContext.SubscriberGotIMyEvent); - Assert.True(rootContext.SubscriberGotMyEvent2); - } - - public class Context : ScenarioContext - { - public bool SubscriberGotIMyEvent { get; set; } - public bool SubscriberGotMyEvent2 { get; set; } - public bool Publisher1HasASubscriberForIMyEvent { get; set; } - public bool Publisher2HasDetectedASubscriberForEvent2 { get; set; } - } - - public class Publisher1 : EndpointConfigurationBuilder - { - public Publisher1() - { - EndpointSetup( b => b.OnEndpointSubscribed((args, context) => - { - context.AddTrace("Publisher1 OnEndpointSubscribed " + args.MessageType); - if (args.MessageType.Contains(typeof(IMyEvent).Name)) - { - context.Publisher1HasASubscriberForIMyEvent = true; - } - })); - } - } - - public class Publisher2 : EndpointConfigurationBuilder - { - public Publisher2() - { - EndpointSetup(b => b.OnEndpointSubscribed((args, context) => - { - context.AddTrace("Publisher2 OnEndpointSubscribed " + args.MessageType); - - if (args.MessageType.Contains(typeof(MyEvent2).Name)) - { - context.Publisher2HasDetectedASubscriberForEvent2 = true; - } - })); - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher1)) - .AddMapping(typeof(Publisher2)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(IMyEvent messageThatIsEnlisted) - { - Context.AddTrace(String.Format("Got event '{0}'", messageThatIsEnlisted)); - if (messageThatIsEnlisted is MyEvent2) - { - Context.SubscriberGotMyEvent2 = true; - } - else - { - Context.SubscriberGotIMyEvent = true; - } - } - } - } - - [Serializable] - public class MyEvent1 : IMyEvent - { - } - - [Serializable] - public class MyEvent2 : IMyEvent - { - } - - public interface IMyEvent : IEvent - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishin.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishin.cs deleted file mode 100644 index e121c052298..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishin.cs +++ /dev/null @@ -1,201 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishin : NServiceBusAcceptanceTest - { - [Test] - public void Issue_1851() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscriber3Subscribed, bus => bus.Publish()) - ) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.Subscriber3Subscribed = true; - } - })) - - .Done(c => c.Subscriber3GotTheEvent) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.Subscriber3GotTheEvent)) - .Run(); - } - - [Test] - public void Should_be_delivered_to_all_subscribers() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscriber1Subscribed && c.Subscriber2Subscribed, (bus, c) => - { - c.AddTrace("Both subscribers is subscribed, going to publish MyEvent"); - bus.Publish(new MyEvent()); - }) - ) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - if (context.HasNativePubSubSupport) - { - context.Subscriber1Subscribed = true; - context.AddTrace("Subscriber1 is now subscribed (at least we have asked the broker to be subscribed)"); - } - else - { - context.AddTrace("Subscriber1 has now asked to be subscribed to MyEvent"); - } - })) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.Subscriber2Subscribed = true; - context.AddTrace("Subscriber2 is now subscribed (at least we have asked the broker to be subscribed)"); - } - else - { - context.AddTrace("Subscriber2 has now asked to be subscribed to MyEvent"); - } - })) - .Done(c => c.Subscriber1GotTheEvent && c.Subscriber2GotTheEvent) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.Subscriber1GotTheEvent); - Assert.True(c.Subscriber2GotTheEvent); - }) - - .Run(TimeSpan.FromSeconds(10)); - } - - public class Context : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - public bool Subscriber2GotTheEvent { get; set; } - public bool Subscriber3GotTheEvent { get; set; } - public bool Subscriber1Subscribed { get; set; } - public bool Subscriber2Subscribed { get; set; } - public bool Subscriber3Subscribed { get; set; } - } - - public class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => - { - b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.Subscriber1Subscribed = true; - context.AddTrace("Subscriber1 is now subscribed"); - } - - - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber2")) - { - context.AddTrace("Subscriber2 is now subscribed"); - context.Subscriber2Subscribed = true; - } - }); - b.DisableFeature(); - }); - } - } - - public class Publisher3 : EndpointConfigurationBuilder - { - public Publisher3() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber3")) - { - context.Subscriber3Subscribed = true; - } - })); - } - } - - public class Subscriber3 : EndpointConfigurationBuilder - { - public Subscriber3() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher3)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(IFoo messageThatIsEnlisted) - { - Context.Subscriber3GotTheEvent = true; - } - } - - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber1GotTheEvent = true; - } - } - } - - public class Subscriber2 : EndpointConfigurationBuilder - { - public Subscriber2() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber2GotTheEvent = true; - } - } - } - - public interface IFoo : IEvent - { - } - - [Serializable] - public class MyEvent : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_an_event_implementing_two_unrelated_interfaces.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_an_event_implementing_two_unrelated_interfaces.cs deleted file mode 100644 index 026ae55a9ed..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_an_event_implementing_two_unrelated_interfaces.cs +++ /dev/null @@ -1,142 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishing_an_event_implementing_two_unrelated_interfaces : NServiceBusAcceptanceTest - { - [Test] - public void Event_should_be_published_using_instance_type() - { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => - b.When(c => c.EventASubscribed && c.EventBSubscribed, (bus, ctx) => - { - var message = new CompositeEvent - { - ContextId = ctx.Id - }; - bus.Publish(message); - })) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.EventASubscribed = true; - context.EventBSubscribed = true; - } - })) - .Done(c => c.GotEventA && c.GotEventB) - .Repeat(r => r.For(Serializers.Xml)) - .Should(c => - { - Assert.True(c.GotEventA); - Assert.True(c.GotEventB); - }) - .Run(TimeSpan.FromSeconds(20)); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - public bool EventASubscribed { get; set; } - public bool EventBSubscribed { get; set; } - public bool GotEventA { get; set; } - public bool GotEventB { get; set; } - } - - public class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber")) - { - if (s.MessageType == typeof(IEventA).AssemblyQualifiedName) - { - context.EventASubscribed = true; - } - if (s.MessageType == typeof(IEventB).AssemblyQualifiedName) - { - context.EventBSubscribed = true; - } - } - })); - } - } - - public class Subscriber : EndpointConfigurationBuilder - { - public Subscriber() - { - EndpointSetup(c => - { - c.Conventions().DefiningMessagesAs(t => t != typeof(CompositeEvent) && typeof(IMessage).IsAssignableFrom(t) && - typeof(IMessage) != t && - typeof(IEvent) != t && - typeof(ICommand) != t); - - c.Conventions().DefiningEventsAs(t => t != typeof(CompositeEvent) && typeof(IEvent).IsAssignableFrom(t) && typeof(IEvent) != t); - c.DisableFeature(); - }) - .AddMapping(typeof(Publisher)) - .AddMapping(typeof(Publisher)); - } - - public class EventAHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(IEventA evnt) - { - if (evnt.ContextId != Context.Id) - { - return; - } - Context.GotEventA = true; - } - } - - public class EventBHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(IEventB evnt) - { - if (evnt.ContextId != Context.Id) - { - return; - } - Context.GotEventB = true; - } - } - } - - class CompositeEvent : IEventA, IEventB - { - public Guid ContextId { get; set; } - public int IntProperty { get; set; } - public string StringProperty { get; set; } - } - - public interface IEventA : IEvent - { - Guid ContextId { get; set; } - string StringProperty { get; set; } - } - - public interface IEventB : IEvent - { - Guid ContextId { get; set; } - int IntProperty { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_from_sendonly.cs deleted file mode 100644 index 0add91ec3f9..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_from_sendonly.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using System.Collections.Generic; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Persistence; - using NServiceBus.Unicast.Subscriptions; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; - using NUnit.Framework; - - public class When_publishing_from_sendonly : NServiceBusAcceptanceTest - { - [Test] - public void Should_be_delivered_to_all_subscribers() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Publish(new MyEvent()))) - .WithEndpoint() - .Done(c => c.SubscriberGotTheEvent) - .Repeat(r => r.For()) - .Should(ctx => Assert.True(ctx.SubscriberGotTheEvent)) - .Run(); - } - - public class Context : ScenarioContext - { - public bool SubscriberGotTheEvent { get; set; } - } - - public class SendOnlyPublisher : EndpointConfigurationBuilder - { - public SendOnlyPublisher() - { - EndpointSetup(b => - { - b.UsePersistence(typeof(HardCodedPersistence)); - b.DisableFeature(); - }).SendOnly(); - } - } - - public class Subscriber : EndpointConfigurationBuilder - { - public Subscriber() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(SendOnlyPublisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.SubscriberGotTheEvent = true; - } - } - } - - [Serializable] - public class MyEvent : IEvent - { - } - - public class HardCodedPersistence : PersistenceDefinition - { - internal HardCodedPersistence() - { - Supports(s => s.EnableFeatureByDefault()); - } - } - - public class HardCodedPersistenceFeature:Feature - { - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } - - public class HardCodedPersistenceImpl : ISubscriptionStorage - { - public void Subscribe(Address client, IEnumerable messageTypes) - { - } - - public void Unsubscribe(Address client, IEnumerable messageTypes) - { - } - - public IEnumerable
GetSubscriberAddressesForMessage(IEnumerable messageTypes) - { - return new[] - { - Address.Parse("publishingfromsendonly.subscriber") - }; - } - - public void Init() - { - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_on_brokers.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_on_brokers.cs deleted file mode 100644 index b39c8a245c3..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_on_brokers.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishing_on_brokers : NServiceBusAcceptanceTest - { - [Test, Ignore] // Ignore because, test this test is unreliable. Passed on the build server without the core fix! - public void Should_be_delivered_to_allsubscribers_without_the_need_for_config() - { - Scenario.Define() - .WithEndpoint - (b => b.When(c => c.IsSubscriptionProcessedForSub1 && c.IsSubscriptionProcessedForSub2, bus => bus.Publish(new MyEvent()))) - .WithEndpoint(b => b.Given((bus, context) => - { - context.IsSubscriptionProcessedForSub1 = true; - })) - .WithEndpoint(b => b.Given((bus, context) => - { - context.IsSubscriptionProcessedForSub2 = true; - })) - .Done(c => c.Subscriber1GotTheEvent && c.Subscriber2GotTheEvent) - .Repeat(r => r.For()) - .Should(c => - { - Assert.True(c.Subscriber1GotTheEvent); - Assert.True(c.Subscriber2GotTheEvent); - }) - - .Run(); - } - - public class Context : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - public bool Subscriber2GotTheEvent { get; set; } - - public bool IsSubscriptionProcessedForSub1 { get; set; } - public bool IsSubscriptionProcessedForSub2 { get; set; } - } - - public class CentralizedPublisher : EndpointConfigurationBuilder - { - public CentralizedPublisher() - { - EndpointSetup(); - } - } - - public class CentralizedSubscriber1 : EndpointConfigurationBuilder - { - public CentralizedSubscriber1() - { - EndpointSetup(); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber1GotTheEvent = true; - } - } - } - - public class CentralizedSubscriber2 : EndpointConfigurationBuilder - { - public CentralizedSubscriber2() - { - EndpointSetup(); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber2GotTheEvent = true; - } - } - } - - [Serializable] - public class MyEvent : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_using_root_type.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_using_root_type.cs deleted file mode 100644 index 4125802e954..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_using_root_type.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishing_using_root_type : NServiceBusAcceptanceTest - { - [Test] - public void Event_should_be_published_using_instance_type() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscriber1Subscribed, bus => - { - IMyEvent message = new EventMessage(); - - bus.Publish(message); - })) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - { - context.Subscriber1Subscribed = true; - } - })) - .Done(c => c.Subscriber1GotTheEvent) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.Subscriber1GotTheEvent)) - .Run(TimeSpan.FromSeconds(20)); - } - - public class Context : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - public bool Subscriber1Subscribed { get; set; } - } - - public class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.Subscriber1Subscribed = true; - } - })); - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(EventMessage messageThatIsEnlisted) - { - Context.Subscriber1GotTheEvent = true; - } - } - } - - [Serializable] - public class EventMessage : IMyEvent - { - public Guid EventId { get; set; } - public DateTime? Time { get; set; } - public TimeSpan Duration { get; set; } - } - - public interface IMyEvent : IEvent - { - Guid EventId { get; set; } - DateTime? Time { get; set; } - TimeSpan Duration { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_only_local_messagehandlers.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_only_local_messagehandlers.cs deleted file mode 100644 index b9aaea671fa..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_only_local_messagehandlers.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using AcceptanceTesting; - using EndpointTemplates; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishing_with_only_local_messagehandlers : NServiceBusAcceptanceTest - { - [Test] - public void Should_trigger_the_catch_all_handler_for_message_driven_subscriptions() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.LocalEndpointSubscribed, bus => bus.Publish(new EventHandledByLocalEndpoint()))) - .Done(c => c.CatchAllHandlerGotTheMessage) - .Repeat(r => r.For()) - .Should(c => Assert.True(c.CatchAllHandlerGotTheMessage)) - .Run(); - } - - [Test] - public void Should_trigger_the_catch_all_handler_for_publishers_with_centralized_pubsub() - { - Scenario.Define() - .WithEndpoint(b => - { - b.Given(bus => bus.Subscribe()); - b.When(c => c.EndpointsStarted, (bus, context) => bus.Publish(new EventHandledByLocalEndpoint())); - }) - .Done(c => c.CatchAllHandlerGotTheMessage) - .Repeat(r => r.For()) - .Should(c => Assert.True(c.CatchAllHandlerGotTheMessage)) - .Run(); - } - - public class Context : ScenarioContext - { - public bool CatchAllHandlerGotTheMessage { get; set; } - - public bool LocalEndpointSubscribed { get; set; } - } - - public class MessageDrivenPublisher : EndpointConfigurationBuilder - { - public MessageDrivenPublisher() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - context.LocalEndpointSubscribed = true; - })) - .AddMapping(typeof(MessageDrivenPublisher)); //an explicit mapping is needed - } - - class CatchAllHandler:IHandleMessages //not enough for auto subscribe to work - { - public Context Context { get; set; } - public void Handle(IEvent message) - { - Context.CatchAllHandlerGotTheMessage = true; - } - } - - class DummyHandler : IHandleMessages //explicit handler for the event is needed - { - public void Handle(EventHandledByLocalEndpoint message) - { - } - } - } - - public class CentralizedStoragePublisher : EndpointConfigurationBuilder - { - public CentralizedStoragePublisher() - { - EndpointSetup(); - } - - class CatchAllHandler : IHandleMessages - { - public Context Context { get; set; } - public void Handle(IEvent message) - { - Context.CatchAllHandlerGotTheMessage = true; - } - } - - class DummyHandler : IHandleMessages //explicit handler for the event is needed - { - public void Handle(EventHandledByLocalEndpoint message) - { - } - } - } - [Serializable] - public class EventHandledByLocalEndpoint : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_overridden_local_address.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_overridden_local_address.cs deleted file mode 100644 index 9cb3a8ac7f0..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_publishing_with_overridden_local_address.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_publishing_with_overridden_local_address : NServiceBusAcceptanceTest - { - [Test, Explicit("This test fails against RabbitMQ")] - public void Should_be_delivered_to_all_subscribers() - { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscriber1Subscribed, bus => bus.Publish(new MyEvent())) - ) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - context.Subscriber1Subscribed = true; - })) - .Done(c => c.Subscriber1GotTheEvent) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.Subscriber1GotTheEvent)) - - .Run(); - } - - public class Context : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - public bool Subscriber1Subscribed { get; set; } - } - - public class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => - { - if (s.SubscriberReturnAddress.Queue.Contains("myinputqueue")) - { - context.Subscriber1Subscribed = true; - } - })); - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(builder => - { - builder.DisableFeature(); - builder.OverrideLocalAddress("myinputqueue"); - }) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber1GotTheEvent = true; - } - } - } - - [Serializable] - public class MyEvent : IEvent - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/PubSub/When_subscribing_to_a_polymorphic_event.cs b/src/NServiceBus.AcceptanceTests/PubSub/When_subscribing_to_a_polymorphic_event.cs deleted file mode 100644 index 3a8b2bf82d6..00000000000 --- a/src/NServiceBus.AcceptanceTests/PubSub/When_subscribing_to_a_polymorphic_event.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace NServiceBus.AcceptanceTests.PubSub -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using Features; - using NUnit.Framework; - - public class When_subscribing_to_a_polymorphic_event : NServiceBusAcceptanceTest - { - [Test] - public void Event_should_be_delivered() - { - var cc = new Context(); - - Scenario.Define(cc) - .WithEndpoint(b => b.When(c => c.Subscriber1Subscribed && c.Subscriber2Subscribed, bus => bus.Publish(new MyEvent()))) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - context.Subscriber1Subscribed = true; - })) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.Subscribe(); - - if (context.HasNativePubSubSupport) - context.Subscriber2Subscribed = true; - })) - .Done(c => c.Subscriber1GotTheEvent && c.Subscriber2GotTheEvent) - .Run(); - - Assert.True(cc.Subscriber1GotTheEvent); - Assert.True(cc.Subscriber2GotTheEvent); - } - - public class Context : ScenarioContext - { - public bool Subscriber1GotTheEvent { get; set; } - - public bool Subscriber2GotTheEvent { get; set; } - - public int NumberOfSubscribers { get; set; } - - public bool Subscriber1Subscribed { get; set; } - - public bool Subscriber2Subscribed { get; set; } - } - - public class Publisher : EndpointConfigurationBuilder - { - public Publisher() - { - EndpointSetup(b => b.OnEndpointSubscribed((args, context) => - { - if (args.SubscriberReturnAddress.Queue.Contains("Subscriber1")) - { - context.Subscriber1Subscribed = true; - } - - if (args.SubscriberReturnAddress.Queue.Contains("Subscriber2")) - { - context.Subscriber2Subscribed = true; - } - })); - } - } - - public class Subscriber1 : EndpointConfigurationBuilder - { - public Subscriber1() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(IMyEvent messageThatIsEnlisted) - { - Context.Subscriber1GotTheEvent = true; - } - } - } - - public class Subscriber2 : EndpointConfigurationBuilder - { - public Subscriber2() - { - EndpointSetup(c => c.DisableFeature()) - .AddMapping(typeof(Publisher)); - } - - public class MyEventHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyEvent messageThatIsEnlisted) - { - Context.Subscriber2GotTheEvent = true; - } - } - } - - [Serializable] - public class MyEvent : IMyEvent - { - } - - public interface IMyEvent : IEvent - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_Subscribing_to_errors.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_Subscribing_to_errors.cs new file mode 100644 index 00000000000..76839723d41 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_Subscribing_to_errors.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using Logging; + using NUnit.Framework; + + public class When_Subscribing_to_errors : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_retain_exception_details_over_immediate_and_delayed_retries() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => + { + b.DoNotFailOnErrorMessages(); + b.When((session, c) => session.SendLocal(new MessageToBeRetried + { + Id = c.Id + })); + }) + .Done(c => c.MessageSentToError) + .Run(); + + Assert.IsInstanceOf(context.MessageSentToErrorException); + Assert.True(context.Logs.Any(l => l.Level == LogLevel.Error && l.Message.Contains("Simulated exception message")), "The last exception should be logged as `error` before sending it to the error queue"); + + // Immediate Retries max retries = 3 means we will be processing 4 times. Delayed Retries max retries = 2 means we will do 3*Immediate Retries + Assert.AreEqual(4*3, context.TotalNumberOfImmediateRetriesTimesInvokedInHandler); + Assert.AreEqual(3*3, context.TotalNumberOfImmediateRetriesEventInvocations); + Assert.AreEqual(2, context.NumberOfDelayedRetriesPerformed); + } + + class Context : ScenarioContext + { + public Guid Id { get; set; } + public int TotalNumberOfImmediateRetriesEventInvocations { get; set; } + public int TotalNumberOfImmediateRetriesTimesInvokedInHandler { get; set; } + public int NumberOfDelayedRetriesPerformed { get; set; } + public bool MessageSentToError { get; set; } + public Exception MessageSentToErrorException { get; set; } + } + + public class DelayedRetriesEndpoint : EndpointConfigurationBuilder + { + public DelayedRetriesEndpoint() + { + EndpointSetup((config, context) => + { + var testContext = (Context) context.ScenarioContext; + var notifications = config.Notifications; + config.EnableFeature(); + var errors = notifications.Errors; + errors.MessageSentToErrorQueue += (sender, message) => + { + testContext.MessageSentToErrorException = message.Exception; + testContext.MessageSentToError = true; + }; + + errors.MessageHasFailedAnImmediateRetryAttempt += (sender, retry) => testContext.TotalNumberOfImmediateRetriesEventInvocations++; + errors.MessageHasBeenSentToDelayedRetries += (sender, retry) => testContext.NumberOfDelayedRetriesPerformed++; + var recoverability = config.Recoverability(); + recoverability.Delayed(settings => + { + settings.NumberOfRetries(2); + settings.TimeIncrease(TimeSpan.FromMilliseconds(1)); + }); + recoverability.Immediate(settings => + { + settings.NumberOfRetries(3); + }); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (message.Id != Context.Id) + { + return Task.FromResult(0); // messages from previous test runs must be ignored + } + + Context.TotalNumberOfImmediateRetriesTimesInvokedInHandler++; + + throw new SimulatedException("Simulated exception message"); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_always_moves_to_error.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_always_moves_to_error.cs new file mode 100644 index 00000000000..b9d553e668c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_always_moves_to_error.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_custom_policy_always_moves_to_error : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_execute_once() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(bus => bus.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageSentToErrorQueue) + .Run(); + + Assert.AreEqual(context.Count, 1); + } + + class Context : ScenarioContext + { + public bool MessageSentToErrorQueue { get; set; } + public int Count { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.EnableFeature(); + configure.Recoverability() + .CustomPolicy((cfg, errorContext) => RecoverabilityAction.MoveToError(cfg.Failed.ErrorQueue)); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => { scenarioContext.MessageSentToErrorQueue = true; }; + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public MessageToBeRetriedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + testContext.Count ++; + throw new SimulatedException(); + } + + Context testContext; + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_does_single_delayed_retry_before_move_to_error.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_does_single_delayed_retry_before_move_to_error.cs new file mode 100644 index 00000000000..dcd2dffc904 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_does_single_delayed_retry_before_move_to_error.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using Transport; + + public class When_custom_policy_does_single_delayed_retry_before_move_to_error : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_execute_twice() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(bus => bus.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageSentToErrorQueue) + .Run(); + + Assert.AreEqual(context.Count, 2); + } + + class Context : ScenarioContext + { + public bool MessageSentToErrorQueue { get; set; } + public int Count { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.EnableFeature(); + configure.Recoverability() + .CustomPolicy(RetryPolicy); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => { scenarioContext.MessageSentToErrorQueue = true; }; + }); + } + + RecoverabilityAction RetryPolicy(RecoverabilityConfig config, ErrorContext context) + { + if (context.DelayedDeliveriesPerformed == 0) + { + return RecoverabilityAction.DelayedRetry(TimeSpan.FromMilliseconds(10)); + } + + return RecoverabilityAction.MoveToError(config.Failed.ErrorQueue); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public MessageToBeRetriedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + testContext.Count ++; + throw new SimulatedException(); + } + + Context testContext; + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_executed.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_executed.cs new file mode 100644 index 00000000000..6d7c3f45843 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_executed.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using Transport; + + public class When_custom_policy_executed : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_provide_error_context_to_policy() + { + var context = await Scenario.Define() + .WithEndpoint(b => + b.When(bus => bus.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.FailedMessages.Any()) + .Run(); + + Assert.That(context.ErrorContexts.Count, Is.EqualTo(2), "because the custom policy should have been invoked twice"); + Assert.That(context.ErrorContexts[0].Message, Is.Not.Null); + Assert.That(context.ErrorContexts[0].Exception, Is.TypeOf()); + Assert.That(context.ErrorContexts[0].DelayedDeliveriesPerformed, Is.EqualTo(0)); + Assert.That(context.ErrorContexts[1].Message, Is.Not.Null); + Assert.That(context.ErrorContexts[1].Exception, Is.TypeOf()); + Assert.That(context.ErrorContexts[1].DelayedDeliveriesPerformed, Is.EqualTo(1)); + } + + class Context : ScenarioContext + { + public List ErrorContexts { get; } = new List(); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + var testContext = (Context) context.ScenarioContext; + + config.EnableFeature(); + config.Recoverability() + .CustomPolicy((cfg, errorContext) => + { + testContext.ErrorContexts.Add(errorContext); + + if (errorContext.DelayedDeliveriesPerformed >= 1) + { + return RecoverabilityAction.MoveToError(cfg.Failed.ErrorQueue); + } + + return RecoverabilityAction.DelayedRetry(TimeSpan.FromMilliseconds(1)); + }); + }); + } + + class Handler : IHandleMessages + { + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_provided.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_provided.cs new file mode 100644 index 00000000000..84220a0b396 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_custom_policy_provided.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_custom_policy_provided : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_pass_recoverability_configuration() + { + var context = await Scenario.Define() + .WithEndpoint(b => + b.When(bus => bus.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.FailedMessages.Any()) + .Run(); + + Assert.That(context.Configuration.Immediate.MaxNumberOfRetries, Is.EqualTo(MaxImmediateRetries)); + Assert.That(context.Configuration.Delayed.MaxNumberOfRetries, Is.EqualTo(MaxDelayedRetries)); + Assert.That(context.Configuration.Delayed.TimeIncrease, Is.EqualTo(DelayedRetryDelayIncrease)); + } + + static TimeSpan DelayedRetryDelayIncrease = TimeSpan.FromMinutes(1); + const int MaxImmediateRetries = 2; + const int MaxDelayedRetries = 2; + + class Context : ScenarioContext + { + public RecoverabilityConfig Configuration { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + var testContext = (Context) context.ScenarioContext; + + config.EnableFeature(); + config.Recoverability() + .Immediate(immediate => immediate.NumberOfRetries(MaxImmediateRetries)) + .Delayed(delayed => delayed.NumberOfRetries(MaxDelayedRetries).TimeIncrease(DelayedRetryDelayIncrease)) + .CustomPolicy((cfg, errorContext) => + { + testContext.Configuration = cfg; + + return RecoverabilityAction.MoveToError(cfg.Failed.ErrorQueue); + }); + }); + } + + class Handler : IHandleMessages + { + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_and_counting.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_and_counting.cs new file mode 100644 index 00000000000..4d9d1f1398b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_and_counting.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_delayed_retries_and_counting : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_reschedule_message_the_number_of_times_configured() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.ForwardedToErrorQueue) + .Run(TimeSpan.FromSeconds(120)); + + Assert.IsTrue(context.ForwardedToErrorQueue); + Assert.AreEqual(ConfiguredNumberOfImmediateRetries, context.Logs.Count(l => l.Message + .StartsWith($"Delayed Retry will reschedule message '{context.PhysicalMessageId}'"))); + } + + const int ConfiguredNumberOfImmediateRetries = 3; + + class Context : ScenarioContext + { + public bool ForwardedToErrorQueue { get; set; } + public string PhysicalMessageId { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.EnableFeature(); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => { scenarioContext.ForwardedToErrorQueue = true; }; + + var recoverability = configure.Recoverability(); + recoverability.Immediate(immediate => immediate.NumberOfRetries(0)); + recoverability.Delayed(settings => settings.TimeIncrease(TimeSpan.FromMilliseconds(1)).NumberOfRetries(ConfiguredNumberOfImmediateRetries)); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public MessageToBeRetriedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + testContext.PhysicalMessageId = context.MessageId; + throw new SimulatedException(); + } + + Context testContext; + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_immediate_retries_disabled.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_immediate_retries_disabled.cs new file mode 100644 index 00000000000..742e172ad8a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_immediate_retries_disabled.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_delayed_retries_with_immediate_retries_disabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_reschedule_message_the_configured_number_of_times() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b + .When((session, ctx) => session.SendLocal(new MessageToBeRetried {Id = ctx.Id})) + .DoNotFailOnErrorMessages()) + .Done(c => c.ReceiveCount >= ConfiguredNumberOfDelayedRetries + 1) + .Run(); + + Assert.AreEqual(ConfiguredNumberOfDelayedRetries + 1, context.ReceiveCount, "Message should be delivered 4 times. Once initially and retried 3 times by Delayed Retries"); + } + + const int ConfiguredNumberOfDelayedRetries = 3; + + class Context : ScenarioContext + { + public Guid Id { get; set; } + public int ReceiveCount { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + configure.EnableFeature(); + var recoverability = configure.Recoverability(); + recoverability.Delayed(settings => settings.TimeIncrease(TimeSpan.FromMilliseconds(1)).NumberOfRetries(ConfiguredNumberOfDelayedRetries)); + recoverability.Immediate(settings => settings.NumberOfRetries(0)); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public MessageToBeRetriedHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (testContext.Id == message.Id) + { + testContext.ReceiveCount++; + + throw new SimulatedException(); + } + + return Task.FromResult(0); + } + + Context testContext; + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_regular_exception.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_regular_exception.cs new file mode 100644 index 00000000000..73ce1f57643 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_regular_exception.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using MessageMutator; + using NUnit.Framework; + + public class When_delayed_retries_with_regular_exception : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_preserve_the_original_body_for_regular_exceptions() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.DelayedRetryChecksum != default(byte)) + .Run(TimeSpan.FromSeconds(120)); + + Assert.AreEqual(context.OriginalBodyChecksum, context.DelayedRetryChecksum, "The body of the message sent to Delayed Retry should be the same as the original message coming off the queue"); + } + + class Context : ScenarioContext + { + public byte OriginalBodyChecksum { get; set; } + public byte DelayedRetryChecksum { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.EnableFeature(); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => { scenarioContext.DelayedRetryChecksum = Checksum(message.Body); }; + configure.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + var recoverability = configure.Recoverability(); + recoverability.Delayed(settings => settings.TimeIncrease(TimeSpan.FromMilliseconds(1))); + }); + } + + public static byte Checksum(byte[] data) + { + var longSum = data.Sum(x => (long) x); + return unchecked((byte) longSum); + } + + class BodyMutator : IMutateOutgoingTransportMessages, IMutateIncomingTransportMessages + { + public BodyMutator(Context testContext) + { + this.testContext = testContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext transportMessage) + { + var originalBody = transportMessage.Body; + + testContext.OriginalBodyChecksum = Checksum(originalBody); + + var decryptedBody = new byte[originalBody.Length]; + + Buffer.BlockCopy(originalBody, 0, decryptedBody, 0, originalBody.Length); + + //decrypt + decryptedBody[0]++; + + transportMessage.Body = decryptedBody; + return Task.FromResult(0); + } + + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + context.OutgoingBody[0]--; + return Task.FromResult(0); + } + + Context testContext; + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_serialization_exception.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_serialization_exception.cs new file mode 100644 index 00000000000..2e6f3c5e20b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_delayed_retries_with_serialization_exception.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using MessageMutator; + using NUnit.Framework; + + public class When_delayed_retries_with_serialization_exception : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_preserve_the_original_body_for_serialization_exceptions() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.DelayedRetryChecksum != default(byte)) + .Run(); + + Assert.AreEqual(context.OriginalBodyChecksum, context.DelayedRetryChecksum, "The body of the message sent to delayed retry should be the same as the original message coming off the queue"); + } + + class Context : ScenarioContext + { + public byte OriginalBodyChecksum { get; set; } + public byte DelayedRetryChecksum { get; set; } + public bool ForwardedToErrorQueue { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var testContext = (Context) context.ScenarioContext; + configure.EnableFeature(); + configure.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => + { + testContext.ForwardedToErrorQueue = true; + testContext.DelayedRetryChecksum = Checksum(message.Body); + }; + configure.Recoverability().Delayed(settings => settings.TimeIncrease(TimeSpan.FromMilliseconds(1))); + }); + } + + static byte Checksum(byte[] data) + { + var longSum = data.Sum(x => (long) x); + return unchecked((byte) longSum); + } + + class BodyMutator : IMutateOutgoingTransportMessages, IMutateIncomingTransportMessages + { + public BodyMutator(Context testContext) + { + this.testContext = testContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext transportMessage) + { + var originalBody = transportMessage.Body; + testContext.OriginalBodyChecksum = Checksum(originalBody); + var newBody = new byte[originalBody.Length]; + Buffer.BlockCopy(originalBody, 0, newBody, 0, originalBody.Length); + //corrupt + newBody[1]++; + transportMessage.Body = newBody; + return Task.FromResult(0); + } + + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + return Task.FromResult(0); + } + + Context testContext; + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_fails_with_retries_set_to_0.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_fails_with_retries_set_to_0.cs new file mode 100644 index 00000000000..9e14ac61f86 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_fails_with_retries_set_to_0.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_fails_with_retries_set_to_0 : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_retry_the_message_using_immediate_retries() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => + { + b.DoNotFailOnErrorMessages(); + b.When((session, c) => session.SendLocal(new MessageToBeRetried + { + ContextId = c.Id + })); + }) + .Done(c => c.GaveUp) + .Run(); + + Assert.AreEqual(1, context.NumberOfTimesInvoked, "No Immediate Retry should be in use if MaxRetries is set to 0"); + } + + class Context : ScenarioContext + { + public Guid Id { get; set; } + public int NumberOfTimesInvoked { get; set; } + + public bool GaveUp { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.Recoverability().Immediate(immediate => immediate.NumberOfRetries(0)); + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => scenarioContext.GaveUp = true; + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (Context.Id != message.ContextId) + { + return Task.FromResult(0); + } + Context.NumberOfTimesInvoked++; + throw new SimulatedException(); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid ContextId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_default_settings.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_default_settings.cs new file mode 100644 index 00000000000..b1be1a072c8 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_default_settings.cs @@ -0,0 +1,84 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_immediate_retries_with_default_settings : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_do_any_retries_if_transactions_are_off() + { + await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b + .When(async (session, context) => + { + await session.SendLocal(new MessageToBeRetried + { + Id = context.Id + }); + await session.SendLocal(new MessageToBeRetried + { + Id = context.Id, + SecondMessage = true + }); + }) + .DoNotFailOnErrorMessages()) + .Done(c => c.SecondMessageReceived || c.NumberOfTimesInvoked > 1) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.AreEqual(1, c.NumberOfTimesInvoked, "No retries should be in use if transactions are off")) + .Run(); + } + + public class Context : ScenarioContext + { + public Guid Id { get; set; } + + public int NumberOfTimesInvoked { get; set; } + + public bool SecondMessageReceived { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((config, context) => { config.UseTransport(context.GetTransportType()).Transactions(TransportTransactionMode.None); }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (message.Id != Context.Id) + { + return Task.FromResult(0); // messages from previous test runs must be ignored + } + + if (message.SecondMessage) + { + Context.SecondMessageReceived = true; + return Task.FromResult(0); + } + + Context.NumberOfTimesInvoked++; + + throw new SimulatedException(); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + + public bool SecondMessage { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_dtc_on.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_dtc_on.cs new file mode 100644 index 00000000000..2f5c3db85c1 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_dtc_on.cs @@ -0,0 +1,87 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_immediate_retries_with_dtc_on : NServiceBusAcceptanceTest + { + [Test] + public Task Should_do_the_configured_number_of_retries_with_dtc_on() + { + return Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b + .When((session, context) => session.SendLocal(new MessageToBeRetried + { + Id = context.Id + })) + .DoNotFailOnErrorMessages()) + .Done(c => c.GaveUpOnRetries) + .Repeat(r => r.For()) + .Should(c => + { + //we add 1 since first call + X retries totals to X+1 + Assert.AreEqual(maxretries + 1, c.NumberOfTimesInvoked, $"The Immediate Retries should retry {maxretries} times"); + Assert.AreEqual(maxretries, c.Logs.Count(l => l.Message + .StartsWith($"Immediate Retry is going to retry message '{c.PhysicalMessageId}' because of an exception:"))); + }) + .Run(); + } + + const int maxretries = 4; + + class Context : ScenarioContext + { + public Guid Id { get; set; } + + public int NumberOfTimesInvoked { get; set; } + + public bool GaveUpOnRetries { get; set; } + + public string PhysicalMessageId { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((b, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + b.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => scenarioContext.GaveUpOnRetries = true; + var recoverability = b.Recoverability(); + recoverability.Immediate(settings => settings.NumberOfRetries(maxretries)); + recoverability.Delayed(settings => settings.TimeIncrease(TimeSpan.FromMilliseconds(1)).NumberOfRetries(3)); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (message.Id != TestContext.Id) + { + return Task.FromResult(0); // messages from previous test runs must be ignored + } + + TestContext.PhysicalMessageId = context.MessageId; + TestContext.NumberOfTimesInvoked++; + + throw new SimulatedException(); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_native_transactions.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_native_transactions.cs new file mode 100644 index 00000000000..6f5303abd3e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_immediate_retries_with_native_transactions.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_immediate_retries_with_native_transactions : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_do_the_configured_number_of_retries_with_native_transactions() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When((session, c) => session.SendLocal(new MessageToBeRetried())) + .DoNotFailOnErrorMessages()) + .Done(c => c.ForwardedToErrorQueue) + .Run(); + + Assert.True(context.ForwardedToErrorQueue); + Assert.AreEqual(5 + 1, context.NumberOfTimesInvoked, "Message should be retried 5 times immediately"); + Assert.AreEqual(5, context.Logs.Count(l => l.Message + .StartsWith($"Immediate Retry is going to retry message '{context.MessageId}' because of an exception:"))); + } + + class Context : ScenarioContext + { + public int NumberOfTimesInvoked { get; set; } + + public bool ForwardedToErrorQueue { get; set; } + + public string MessageId { get; set; } + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((config, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + config.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => scenarioContext.ForwardedToErrorQueue = true; + + var recoverability = config.Recoverability(); + recoverability.Immediate(immediate => immediate.NumberOfRetries(5)); + recoverability.Delayed(delayed => delayed.NumberOfRetries(0)); //disable the delayed retries + + config.UseTransport(context.GetTransportType()) + .Transactions(TransportTransactionMode.ReceiveOnly); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + TestContext.MessageId = context.MessageId; + TestContext.NumberOfTimesInvoked++; + + throw new SimulatedException(); + } + } + } + + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_fails_retries.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_fails_retries.cs new file mode 100644 index 00000000000..85982a9ad8f --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_fails_retries.cs @@ -0,0 +1,82 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using EndpointTemplates; + using NUnit.Framework; + + public class When_message_fails_retries : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_forward_message_to_error_queue() + { + MessagesFailedException exception = null; + try + { + await Scenario.Define() + .WithEndpoint(b => b + .When((session, c) => session.SendLocal(new MessageWhichFailsRetries()))) + .Done(c => c.ForwardedToErrorQueue) + .Run(); + } + catch (AggregateException ex) + { + exception = ex.ExpectFailedMessages(); + } + + Assert.AreEqual(1, exception.FailedMessages.Count); + var failedMessage = exception.FailedMessages.Single(); + + var testContext = (Context) exception.ScenarioContext; + Assert.AreEqual(typeof(MessageWhichFailsRetries).AssemblyQualifiedName, failedMessage.Headers[Headers.EnclosedMessageTypes]); + Assert.AreEqual(testContext.PhysicalMessageId, failedMessage.MessageId); + Assert.IsAssignableFrom(typeof(SimulatedException), failedMessage.Exception); + + Assert.AreEqual(1, testContext.Logs.Count(l => l.Message + .StartsWith($"Moving message '{testContext.PhysicalMessageId}' to the error queue 'error' because processing failed due to an exception:"))); + } + + public class RetryEndpoint : EndpointConfigurationBuilder + { + public RetryEndpoint() + { + EndpointSetup((configure, context) => + { + var scenarioContext = (Context) context.ScenarioContext; + configure.Notifications.Errors.MessageSentToErrorQueue += (sender, message) => scenarioContext.ForwardedToErrorQueue = true; + }); + } + + public static byte Checksum(byte[] data) + { + var longSum = data.Sum(x => (long) x); + return unchecked((byte) longSum); + } + + class MessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageWhichFailsRetries message, IMessageHandlerContext context) + { + TestContext.PhysicalMessageId = context.MessageId; + throw new SimulatedException(); + } + } + } + + class Context : ScenarioContext + { + public bool ForwardedToErrorQueue { get; set; } + + public string PhysicalMessageId { get; set; } + } + + public class MessageWhichFailsRetries : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_is_deferred_by_delayed_retries_using_dtc.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_is_deferred_by_delayed_retries_using_dtc.cs new file mode 100644 index 00000000000..8eac425a0da --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/When_message_is_deferred_by_delayed_retries_using_dtc.cs @@ -0,0 +1,110 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_message_is_deferred_by_delayed_retries_using_dtc : NServiceBusAcceptanceTest + { + [Test] + public Task Should_not_commit_distributed_transaction() + { + return Scenario.Define(c => c.Id = Guid.NewGuid()) + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((session, context) => session.SendLocal(new MessageToFail + { + Id = context.Id + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Repeat(r => r.For()) + .Should(c => Assert.Greater(c.NumberOfProcessingAttempts, 1, "Should retry at least once")) + .Should(c => Assert.That(c.TransactionStatuses, Is.All.Not.EqualTo(TransactionStatus.Committed))) + .Run(); + } + + const string ErrorQueueName = "error_spy_queue"; + + class Context : ScenarioContext + { + public Guid Id { get; set; } + public bool MessageMovedToErrorQueue { get; set; } + public List TransactionStatuses { get; } = new List(); + public int NumberOfProcessingAttempts { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => + { + config.EnableFeature(); + config.SendFailedMessagesTo(ErrorQueueName); + var recoverability = config.Recoverability(); + recoverability.Delayed(settings => + { + settings.NumberOfRetries(3); + settings.TimeIncrease(TimeSpan.FromSeconds(1)); + }); + }); + } + + class FailingHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToFail message, IMessageHandlerContext context) + { + if (message.Id == TestContext.Id) + { + TestContext.NumberOfProcessingAttempts++; + + Transaction.Current.TransactionCompleted += CaptureTransactionStatus; + } + + throw new SimulatedException(); + } + + void CaptureTransactionStatus(object sender, TransactionEventArgs args) + { + TestContext.TransactionStatuses.Add(args.Transaction.TransactionInformation.Status); + } + } + } + + class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup().CustomEndpointName(ErrorQueueName); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToFail message, IMessageHandlerContext context) + { + if (message.Id == TestContext.Id) + { + TestContext.MessageMovedToErrorQueue = true; + } + return Task.FromResult(0); + } + } + } + + class MessageToFail : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/Retries/when_immediate_retries_fail.cs b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/when_immediate_retries_fail.cs new file mode 100644 index 00000000000..d8c049e01c6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/Retries/when_immediate_retries_fail.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.AcceptanceTests.Recoverability.Retries +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class when_immediate_retries_fail : NServiceBusAcceptanceTest + { + [Test] + public Task Should_do_delayed_retries() + { + return Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b + .When((session, context) => session.SendLocal(new MessageToBeRetried + { + Id = context.Id + })) + .DoNotFailOnErrorMessages()) + .Done(c => c.NumberOfTimesInvoked >= 2) + .Repeat(r => r.For(Transports.Default)) + .Should(context => + { + Assert.GreaterOrEqual(1, context.NumberOfDelayedRetriesPerformed, "Should only do one retry"); + Assert.GreaterOrEqual(context.TimeOfSecondAttempt - context.TimeOfFirstAttempt, Delay, "Should delay the retry"); + }) + .Run(); + } + + static TimeSpan Delay = TimeSpan.FromMilliseconds(1); + + class Context : ScenarioContext + { + public Guid Id { get; set; } + + public int NumberOfTimesInvoked { get; set; } + + public DateTime TimeOfFirstAttempt { get; set; } + public DateTime TimeOfSecondAttempt { get; set; } + + public int NumberOfDelayedRetriesPerformed { get; set; } + } + + public class DelayedRetryEndpoint : EndpointConfigurationBuilder + { + public DelayedRetryEndpoint() + { + EndpointSetup(config => + { + config.EnableFeature(); + config.Recoverability().Delayed(settings => + { + settings.NumberOfRetries(1); + settings.TimeIncrease(Delay); + }); + }); + } + + class MessageToBeRetriedHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToBeRetried message, IMessageHandlerContext context) + { + if (message.Id != TestContext.Id) + { + return Task.FromResult(0); // messages from previous test runs must be ignored + } + + TestContext.NumberOfTimesInvoked++; + + if (TestContext.NumberOfTimesInvoked == 1) + { + TestContext.TimeOfFirstAttempt = DateTime.UtcNow; + } + + if (TestContext.NumberOfTimesInvoked == 2) + { + TestContext.TimeOfSecondAttempt = DateTime.UtcNow; + } + + string retries; + + if (context.MessageHeaders.TryGetValue(Headers.DelayedRetries, out retries)) + { + TestContext.NumberOfDelayedRetriesPerformed = int.Parse(retries); + } + + throw new SimulatedException(); + } + } + } + + [Serializable] + public class MessageToBeRetried : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_custom_policy_moves_to_overridden_error_queue.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_custom_policy_moves_to_overridden_error_queue.cs new file mode 100644 index 00000000000..10bf26de888 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_custom_policy_moves_to_overridden_error_queue.cs @@ -0,0 +1,87 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NUnit.Framework; + + public class When_custom_policy_moves_to_overridden_error_queue : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_move_to_defined_error_queue() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .DoNotFailOnErrorMessages() + .When((session, ctx) => session.SendLocal(new InitiatingMessage + { + Id = ctx.TestRunId + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Run(); + + Assert.IsTrue(context.MessageMovedToErrorQueue); + } + + class Context : ScenarioContext + { + public bool MessageMovedToErrorQueue { get; set; } + } + + class EndpointWithFailingHandler : EndpointConfigurationBuilder + { + public EndpointWithFailingHandler() + { + EndpointSetup((config, context) => + { + config.Recoverability().CustomPolicy((c, ec) => + RecoverabilityAction.MoveToError(Conventions.EndpointNamingConvention(typeof(ErrorSpy)))); + + config.SendFailedMessagesTo("error"); + }); + } + + class InitiatingHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup(); + } + + class InitiatingMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + if (initiatingMessage.Id == TestContext.TestRunId) + { + TestContext.MessageMovedToErrorQueue = true; + } + + return Task.FromResult(0); + } + } + } + + class InitiatingMessage : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_error_is_overridden_in_code.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_error_is_overridden_in_code.cs new file mode 100644 index 00000000000..135c6daf94b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_error_is_overridden_in_code.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_error_is_overridden_in_code : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_error_to_target_queue() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new Message())) + .DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.MessageReceived) + .Run(); + + Assert.True(context.MessageReceived); + } + + public class UserEndpoint : EndpointConfigurationBuilder + { + public UserEndpoint() + { + EndpointSetup(b => { b.SendFailedMessagesTo("error_with_code_source"); }); + } + + class Handler : IHandleMessages + { + public Task Handle(Message message, IMessageHandlerContext context) + { + throw new SimulatedException(); + } + } + } + + public class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup() + .CustomEndpointName("error_with_code_source"); + } + + class Handler : IHandleMessages + { + public Context MyContext { get; set; } + + public Task Handle(Message message, IMessageHandlerContext context) + { + MyContext.MessageReceived = true; + return Task.FromResult(0); + } + } + } + + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + } + + [Serializable] + public class Message : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue.cs new file mode 100644 index 00000000000..17db41da9da --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue.cs @@ -0,0 +1,180 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_message_is_moved_to_error_queue : NServiceBusAcceptanceTest + { + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public Task Should_not_send_outgoing_messages(TransportTransactionMode transactionMode) + { + return Scenario.Define(c => { c.TransactionMode = transactionMode; }) + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((session, context) => session.SendLocal(new InitiatingMessage + { + Id = context.TestRunId + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Repeat(r => r.For()) + .Should(c => Assert.IsFalse(c.OutgoingMessageSent, "Outgoing messages should not be sent")) + .Run(); + } + + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.None)] + public Task May_send_outgoing_messages(TransportTransactionMode transactionMode) + { + return Scenario.Define(c => { c.TransactionMode = transactionMode; }) + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((session, context) => session.SendLocal(new InitiatingMessage + { + Id = context.TestRunId + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Repeat(r => r.For()) + .Run(); + } + + [Test] + public async Task Should_log_exception() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .DoNotFailOnErrorMessages() + .When((session, ctx) => session.SendLocal(new InitiatingMessage + { + Id = ctx.TestRunId + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Run(); + + Assert.That(context.Logs, Has.Some.Message.Match($"Moving message .+ to the error queue '{ Conventions.EndpointNamingConvention(typeof(ErrorSpy)) }' because processing failed due to an exception: NServiceBus.AcceptanceTesting.SimulatedException:")); + } + + class Context : ScenarioContext + { + public bool MessageMovedToErrorQueue { get; set; } + public bool OutgoingMessageSent { get; set; } + public TransportTransactionMode TransactionMode { get; set; } + } + + class EndpointWithOutgoingMessages : EndpointConfigurationBuilder + { + public EndpointWithOutgoingMessages() + { + EndpointSetup((config, context) => + { + var testContext = context.ScenarioContext as Context; + + config.UseTransport(context.GetTransportType()) + .Transactions(testContext.TransactionMode); + config.Pipeline.Register(new ThrowingBehavior(), "Behavior that always throws"); + config.SendFailedMessagesTo(Conventions.EndpointNamingConvention(typeof(ErrorSpy))); + }); + } + + class InitiatingHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public async Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + if (initiatingMessage.Id == TestContext.TestRunId) + { + await context.Send(Conventions.EndpointNamingConvention(typeof(ErrorSpy)), new SubsequentMessage + { + Id = initiatingMessage.Id + }); + } + } + } + } + + class EndpointWithFailingHandler : EndpointConfigurationBuilder + { + public EndpointWithFailingHandler() + { + EndpointSetup((config, context) => { config.SendFailedMessagesTo(Conventions.EndpointNamingConvention(typeof(ErrorSpy))); }); + } + + class InitiatingMessageHandler : IHandleMessages + { + public Task Handle(InitiatingMessage message, IMessageHandlerContext context) + { + throw new SimulatedException("message should be moved to the error queue"); + } + } + } + + class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup(config => config.LimitMessageProcessingConcurrencyTo(1)); + } + + class InitiatingMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + if (initiatingMessage.Id == TestContext.TestRunId) + { + TestContext.MessageMovedToErrorQueue = true; + } + + return Task.FromResult(0); + } + } + + class SubsequentMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(SubsequentMessage message, IMessageHandlerContext context) + { + if (message.Id == TestContext.TestRunId) + { + TestContext.OutgoingMessageSent = true; + } + + return Task.FromResult(0); + } + } + } + + class ThrowingBehavior : IBehavior + { + public async Task Invoke(ITransportReceiveContext context, Func next) + { + await next(context).ConfigureAwait(false); + + throw new SimulatedException(); + } + } + + class InitiatingMessage : IMessage + { + public Guid Id { get; set; } + } + + class SubsequentMessage : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_using_dtc.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_using_dtc.cs new file mode 100644 index 00000000000..455fe59047b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_using_dtc.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_message_is_moved_to_error_queue_using_dtc : NServiceBusAcceptanceTest + { + [Test] + public Task Should_not_commit_distributed_transaction() + { + return Scenario.Define(c => c.Id = Guid.NewGuid()) + .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .When((session, context) => session.SendLocal(new MessageToFail + { + Id = context.Id + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Repeat(r => r.For()) + .Should(c => Assert.That(c.TransactionStatuses, Is.All.Not.EqualTo(TransactionStatus.Committed))) + .Run(); + } + + const string ErrorQueueName = "error_spy_queue"; + + class Context : ScenarioContext + { + public Guid Id { get; set; } + public bool MessageMovedToErrorQueue { get; set; } + public List TransactionStatuses { get; } = new List(); + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(config => + { + config.SendFailedMessagesTo(ErrorQueueName); + }); + } + + class FailingHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToFail message, IMessageHandlerContext context) + { + if (message.Id == TestContext.Id) + { + Transaction.Current.TransactionCompleted += CaptureTransactionStatus; + } + + throw new SimulatedException(); + } + + void CaptureTransactionStatus(object sender, TransactionEventArgs args) + { + TestContext.TransactionStatuses.Add(args.Transaction.TransactionInformation.Status); + } + } + } + + class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup().CustomEndpointName(ErrorQueueName); + } + + class Handler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageToFail message, IMessageHandlerContext context) + { + if (message.Id == TestContext.Id) + { + TestContext.MessageMovedToErrorQueue = true; + } + + return Task.FromResult(0); + } + } + } + + class MessageToFail : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_with_header_customizations.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_with_header_customizations.cs new file mode 100644 index 00000000000..4fa33a3cd49 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_is_moved_to_error_queue_with_header_customizations.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NUnit.Framework; + + public class When_message_is_moved_to_error_queue_with_header_customizations : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_apply_header_customizations() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .DoNotFailOnErrorMessages() + .When((session, ctx) => session.SendLocal(new InitiatingMessage + { + Id = ctx.TestRunId + })) + ) + .WithEndpoint() + .Done(c => c.MessageMovedToErrorQueue) + .Run(); + + Assert.IsFalse(context.Headers.ContainsKey("NServiceBus.ExceptionInfo.ExceptionType")); + Assert.IsTrue(context.Headers.ContainsKey("NServiceBus.ExceptionInfo.Message")); + Assert.AreEqual("this is a large message", context.Headers["NServiceBus.ExceptionInfo.Message"]); + Assert.IsTrue(context.Headers.ContainsKey("NServiceBus.ExceptionInfo.NotInventedHere")); + Assert.AreEqual("NotInventedHere", context.Headers["NServiceBus.ExceptionInfo.NotInventedHere"]); + } + + class Context : ScenarioContext + { + public bool MessageMovedToErrorQueue { get; set; } + public IReadOnlyDictionary Headers { get; set; } + } + + class EndpointWithFailingHandler : EndpointConfigurationBuilder + { + public EndpointWithFailingHandler() + { + EndpointSetup((config, context) => + { + config.Recoverability() + .Failed(failed => failed.HeaderCustomization(headers => + { + headers.Remove("NServiceBus.ExceptionInfo.ExceptionType"); + headers["NServiceBus.ExceptionInfo.Message"] = headers["NServiceBus.ExceptionInfo.Message"].ToLower(); + headers["NServiceBus.ExceptionInfo.NotInventedHere"] = "NotInventedHere"; + })); + + config.SendFailedMessagesTo(Conventions.EndpointNamingConvention(typeof(ErrorSpy))); + }); + } + + class InitiatingHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + throw new SimulatedException("THIS IS A LARGE MESSAGE"); + } + } + } + + class ErrorSpy : EndpointConfigurationBuilder + { + public ErrorSpy() + { + EndpointSetup(); + } + + class InitiatingMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(InitiatingMessage initiatingMessage, IMessageHandlerContext context) + { + if (initiatingMessage.Id == TestContext.TestRunId) + { + TestContext.Headers = context.MessageHeaders; + TestContext.MessageMovedToErrorQueue = true; + } + + return Task.FromResult(0); + } + } + } + + class InitiatingMessage : IMessage + { + public Guid Id { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Recoverability/When_message_with_TimeToBeReceived_fails.cs b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_with_TimeToBeReceived_fails.cs new file mode 100644 index 00000000000..82016f4fb07 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Recoverability/When_message_with_TimeToBeReceived_fails.cs @@ -0,0 +1,106 @@ +namespace NServiceBus.AcceptanceTests.Recoverability +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_message_with_TimeToBeReceived_fails : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_honor_TimeToBeReceived_for_error_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageThatFails())) + .DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.MessageFailed && c.TTBRHasExpiredAndMessageIsStillInErrorQueue) + .Run(); + + Assert.IsTrue(context.MessageFailed); + Assert.IsTrue(context.TTBRHasExpiredAndMessageIsStillInErrorQueue); + } + + class Context : ScenarioContext + { + public bool MessageFailed { get; set; } + public DateTime? FirstTimeProcessedByErrorHandler { get; set; } + public bool TTBRHasExpiredAndMessageIsStillInErrorQueue { get; set; } + } + + class EndpointThatThrows : EndpointConfigurationBuilder + { + public EndpointThatThrows() + { + EndpointSetup(b => { b.SendFailedMessagesTo("errorQueueForAcceptanceTest"); }); + } + + class ThrowingMessageHandler : IHandleMessages + { + public ThrowingMessageHandler(Context context) + { + this.context = context; + } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context1) + { + context.MessageFailed = true; + throw new SimulatedException(); + } + + Context context; + } + } + + class EndpointThatHandlesErrorMessages : EndpointConfigurationBuilder + { + public EndpointThatHandlesErrorMessages() + { + EndpointSetup() + .CustomEndpointName("errorQueueForAcceptanceTest"); + } + + class ErrorMessageHandler : IHandleMessages + { + public ErrorMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MessageThatFails message, IMessageHandlerContext context) + { + var errorProcessingStarted = DateTime.Now; + if (testContext.FirstTimeProcessedByErrorHandler == null) + { + testContext.FirstTimeProcessedByErrorHandler = errorProcessingStarted; + } + + var ttbr = TimeSpan.Parse(context.MessageHeaders[Headers.TimeToBeReceived]); + var ttbrExpired = errorProcessingStarted > testContext.FirstTimeProcessedByErrorHandler.Value + ttbr; + if (ttbrExpired) + { + testContext.TTBRHasExpiredAndMessageIsStillInErrorQueue = true; + var timeElapsedSinceFirstHandlingOfErrorMessage = errorProcessingStarted - testContext.FirstTimeProcessedByErrorHandler.Value; + Console.WriteLine("Error message not removed because of TTBR({0}) after {1}. Succeeded.", ttbr, timeElapsedSinceFirstHandlingOfErrorMessage); + } + else + { + return context.HandleCurrentMessageLater(); + } + + return Task.FromResult(0); // ignore messages from previous test runs + } + + Context testContext; + } + } + + [Serializable] + [TimeToBeReceived("00:00:03")] + class MessageThatFails : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_duplicate_message_arrives.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_duplicate_message_arrives.cs new file mode 100644 index 00000000000..54e2caaa33e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_duplicate_message_arrives.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_a_duplicate_message_arrives : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_dispatch_messages_already_dispatched() + { + await Scenario.Define() + .WithEndpoint(b => b.When(async session => + { + var duplicateMessageId = Guid.NewGuid().ToString(); + + var options = new SendOptions(); + + options.SetMessageId(duplicateMessageId); + options.RouteToThisEndpoint(); + + await session.Send(new PlaceOrder(), options); + await session.Send(new PlaceOrder(), options); + await session.SendLocal(new PlaceOrder + { + Terminator = true + }); + })) + .WithEndpoint() + .Done(c => c.Done) + .Repeat(r => r.For()) + .Should(context => Assert.AreEqual(2, context.MessagesReceivedByDownstreamEndpoint)) + .Run(); + } + + public class Context : ScenarioContext + { + public int MessagesReceivedByDownstreamEndpoint { get; set; } + public bool Done { get; set; } + } + + public class DownstreamEndpoint : EndpointConfigurationBuilder + { + public DownstreamEndpoint() + { + EndpointSetup(b => { b.LimitMessageProcessingConcurrencyTo(1); }); + } + + class SendOrderAcknowledgementHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SendOrderAcknowledgement message, IMessageHandlerContext context) + { + Context.MessagesReceivedByDownstreamEndpoint++; + if (message.Terminator) + { + Context.Done = true; + } + return Task.FromResult(0); + } + } + } + + public class OutboxEndpoint : EndpointConfigurationBuilder + { + public OutboxEndpoint() + { + EndpointSetup(b => + { + b.LimitMessageProcessingConcurrencyTo(1); // We limit to one to avoid race conditions on dispatch and this allows us to reliable check whether deduplication happens properly + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + }).AddMapping(typeof(DownstreamEndpoint)); + } + + class PlaceOrderHandler : IHandleMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + return context.Send(new SendOrderAcknowledgement + { + Terminator = message.Terminator + }); + } + } + } + + class PlaceOrder : ICommand + { + public bool Terminator { get; set; } + } + + class SendOrderAcknowledgement : IMessage + { + public bool Terminator { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_message_is_audited.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_message_is_audited.cs new file mode 100644 index 00000000000..751fc3a6eb6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_a_message_is_audited.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_a_message_is_audited : NServiceBusAcceptanceTest + { + [Test] + public Task Should_be_dispatched_immediately() + { + return Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageToBeAudited())) + .DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.MessageAudited) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.MessageAudited)) + .Run(); + } + + class Context : ScenarioContext + { + public bool MessageAudited { get; set; } + } + + class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + b.Pipeline.Register("BlowUpAfterDispatchBehavior", new BlowUpAfterDispatchBehavior(), "For testing"); + }) + .AuditTo(); + } + + class BlowUpAfterDispatchBehavior : IBehavior + { + public async Task Invoke(IBatchDispatchContext context, Func next) + { + if (!context.Operations.Any(op => op.Message.Headers[Headers.EnclosedMessageTypes].Contains(typeof(MessageToBeAudited).Name))) + { + await next(context).ConfigureAwait(false); + return; + } + + await next(context).ConfigureAwait(false); + + throw new SimulatedException(); + } + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + class AuditSpyEndpoint : EndpointConfigurationBuilder + { + public AuditSpyEndpoint() + { + EndpointSetup(); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeAudited message, IMessageHandlerContext context) + { + Context.MessageAudited = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MessageToBeAudited : IMessage + { + public string RunId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_blowing_up_just_after_dispatch.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_blowing_up_just_after_dispatch.cs new file mode 100644 index 00000000000..c9a81c2386a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_blowing_up_just_after_dispatch.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_blowing_up_just_after_dispatch : NServiceBusAcceptanceTest + { + [Test] + public Task Should_still_release_the_outgoing_messages_to_the_transport() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new PlaceOrder()))) + .Done(c => c.OrderAckReceived == 1) + .Repeat(r => r.For()) + .Should(context => Assert.AreEqual(1, context.OrderAckReceived, "Order ack should have been received since outbox dispatch isn't part of the receive tx")) + .Run(TimeSpan.FromSeconds(20)); + } + + public class Context : ScenarioContext + { + public int OrderAckReceived { get; set; } + } + + public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder + { + public NonDtcReceivingEndpoint() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + b.Pipeline.Register("BlowUpAfterDispatchBehavior", new BlowUpAfterDispatchBehavior(), "For testing"); + }); + } + + class BlowUpAfterDispatchBehavior : IBehavior + { + public async Task Invoke(IBatchDispatchContext context, Func next) + { + if (!context.Operations.Any(op => op.Message.Headers[Headers.EnclosedMessageTypes].Contains(typeof(PlaceOrder).Name))) + { + await next(context).ConfigureAwait(false); + return; + } + + if (called) + { + Console.WriteLine("Called once, skipping next"); + return; + } + + await next(context).ConfigureAwait(false); + + called = true; + + throw new SimulatedException(); + } + + static bool called; + } + + class PlaceOrderHandler : IHandleMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + return context.SendLocal(new SendOrderAcknowledgment()); + } + } + + class SendOrderAcknowledgmentHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SendOrderAcknowledgment message, IMessageHandlerContext context) + { + Context.OrderAckReceived++; + return Task.FromResult(0); + } + } + } + + public class PlaceOrder : ICommand + { + } + + class SendOrderAcknowledgment : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_clearing_saga_timeouts.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_clearing_saga_timeouts.cs new file mode 100644 index 00000000000..f894d292a9d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_clearing_saga_timeouts.cs @@ -0,0 +1,143 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus.Outbox; + using NServiceBus; + using NUnit.Framework; + using Persistence; + using ScenarioDescriptors; + + public class When_clearing_saga_timeouts : NServiceBusAcceptanceTest + { + [Test] + public Task Should_record_the_request_to_clear_in_outbox() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new PlaceOrder + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.Done) + .Repeat(b => b.For()) + .Should(ctx => Assert.AreEqual(2, ctx.NumberOfOps, "Request to clear and a done signal should be in the outbox")) + .Run(); + } + + public class Context : ScenarioContext + { + public int NumberOfOps { get; set; } + public bool Done { get; set; } + } + + public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder + { + public NonDtcReceivingEndpoint() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableFeature(); + b.UsePersistence(); + b.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + class DoneHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SignalDone message, IMessageHandlerContext context) + { + Context.Done = true; + return Task.FromResult(0); + } + } + + class PlaceOrderSaga : Saga, IAmStartedByMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + MarkAsComplete(); + return context.SendLocal(new SignalDone()); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); + } + + public class PlaceOrderSagaData : ContainSagaData + { + public virtual Guid DataId { get; set; } + } + } + } + + class FakeOutbox : IOutboxStorage + { + public FakeOutbox(Context context) + { + testContext = context; + } + + public Task Get(string messageId, ContextBag context) + { + return Task.FromResult(default(OutboxMessage)); + } + + public Task Store(OutboxMessage message, OutboxTransaction transaction, ContextBag context) + { + testContext.NumberOfOps += message.TransportOperations.Length; + return Task.FromResult(0); + } + + public Task SetAsDispatched(string messageId, ContextBag context) + { + return Task.FromResult(0); + } + + public Task BeginTransaction(ContextBag context) + { + return Task.FromResult(new FakeOutboxTransaction()); + } + + Context testContext; + + class FakeOutboxTransaction : OutboxTransaction + { + public void Dispose() + { + } + + public Task Commit() + { + return Task.FromResult(0); + } + } + } + + public class PlaceOrder : ICommand + { + public Guid DataId { get; set; } + } + + public class SignalDone : ICommand + { + } + } + + public class FakeOutboxPersistence : PersistenceDefinition + { + public FakeOutboxPersistence() + { + Supports(s => { }); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_dispatching_forwarded_messages.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_dispatching_forwarded_messages.cs new file mode 100644 index 00000000000..9cfc9a1f98b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_dispatching_forwarded_messages.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_dispatching_forwarded_messages : NServiceBusAcceptanceTest + { + [Test] + public Task Should_be_dispatched_immediately() + { + return Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new MessageToBeForwarded())) + .DoNotFailOnErrorMessages()) + .WithEndpoint() + .Done(c => c.Done) + .Repeat(r => r.For()) + .Should(c => Assert.IsTrue(c.Done)) + .Run(); + } + + class Context : ScenarioContext + { + public bool Done { get; set; } + } + + class EndpointWithAuditOn : EndpointConfigurationBuilder + { + public EndpointWithAuditOn() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + b.Pipeline.Register("BlowUpAfterDispatchBehavior", new BlowUpAfterDispatchBehavior(), "For testing"); + b.ForwardReceivedMessagesTo("forward_receiver_outbox"); + }); + } + + class BlowUpAfterDispatchBehavior : IBehavior + { + public async Task Invoke(IBatchDispatchContext context, Func next) + { + if (!context.Operations.Any(op => op.Message.Headers[Headers.EnclosedMessageTypes].Contains(typeof(MessageToBeForwarded).Name))) + { + await next(context).ConfigureAwait(false); + return; + } + + await next(context).ConfigureAwait(false); + + throw new SimulatedException(); + } + } + + public class MessageToBeForwardedHandler : IHandleMessages + { + public Task Handle(MessageToBeForwarded message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + class ForwardingSpyEndpoint : EndpointConfigurationBuilder + { + public ForwardingSpyEndpoint() + { + EndpointSetup() + .CustomEndpointName("forward_receiver_outbox"); + } + + public class MessageToBeAuditedHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeForwarded message, IMessageHandlerContext context) + { + Context.Done = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MessageToBeForwarded : IMessage + { + public string RunId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_receiving_a_message.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_receiving_a_message.cs new file mode 100644 index 00000000000..c5babc17ca2 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_receiving_a_message.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_receiving_a_message_not_found_in_the_outbox : NServiceBusAcceptanceTest + { + [Test] + public Task Should_handle_it() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new PlaceOrder()))) + .Done(c => c.OrderAckReceived == 1) + .Repeat(r => r.For()) + .Run(TimeSpan.FromSeconds(20)); + } + + class Context : ScenarioContext + { + public int OrderAckReceived { get; set; } + } + + public class NonDtcReceivingEndpoint : EndpointConfigurationBuilder + { + public NonDtcReceivingEndpoint() + { + EndpointSetup(b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + }); + } + + class PlaceOrderHandler : IHandleMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + return context.SendLocal(new SendOrderAcknowledgement()); + } + } + + class SendOrderAcknowledgementHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SendOrderAcknowledgement message, IMessageHandlerContext context) + { + Context.OrderAckReceived++; + return Task.FromResult(0); + } + } + } + + class PlaceOrder : ICommand + { + } + + class SendOrderAcknowledgement : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_sending_from_a_non_dtc_endpoint.cs b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_sending_from_a_non_dtc_endpoint.cs new file mode 100644 index 00000000000..873be5eec53 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Reliability/Outbox/When_sending_from_a_non_dtc_endpoint.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Reliability.Outbox +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_sending_from_a_non_dtc_endpoint : NServiceBusAcceptanceTest + { + [Test] + public Task Should_store_them_and_dispatch_them_from_the_outbox() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new PlaceOrder()))) + .Done(c => c.OrderAckReceived) + .Repeat(r => r.For()) + .Should(context => Assert.IsTrue(context.OrderAckReceived)) + .Run(TimeSpan.FromSeconds(20)); + } + + public class Context : ScenarioContext + { + public bool OrderAckReceived { get; set; } + } + + public class NonDtcSalesEndpoint : EndpointConfigurationBuilder + { + public NonDtcSalesEndpoint() + { + EndpointSetup( + b => + { + b.GetSettings().Set("DisableOutboxTransportCheck", true); + b.EnableOutbox(); + }); + } + + class PlaceOrderHandler : IHandleMessages + { + public Task Handle(PlaceOrder message, IMessageHandlerContext context) + { + return context.SendLocal(new SendOrderAcknowledgement()); + } + } + + class SendOrderAcknowledgementHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SendOrderAcknowledgement message, IMessageHandlerContext context) + { + Context.OrderAckReceived = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + class PlaceOrder : ICommand + { + } + + [Serializable] + class SendOrderAcknowledgement : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_Subscribing_to_errors.cs b/src/NServiceBus.AcceptanceTests/Retries/When_Subscribing_to_errors.cs deleted file mode 100644 index 4f4bf613c2d..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_Subscribing_to_errors.cs +++ /dev/null @@ -1,140 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using System.Collections.Generic; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NUnit.Framework; - - public class When_Subscribing_to_errors : NServiceBusAcceptanceTest - { - [Test] - public void Should_contain_exception_details() - { - var context = new Context - { - Id = Guid.NewGuid() - }; - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MessageToBeRetried - { - Id = c.Id - }))) - .AllowExceptions(e => e.Message.Contains("Simulated exception")) - .Done(c => c.MessageSentToError) - .Run(TimeSpan.FromMinutes(5)); - - Assert.IsInstanceOf(context.MessageSentToErrorException); - } - - [Test] - public void Should_receive_notifications() - { - var context = new Context - { - Id = Guid.NewGuid() - }; - Scenario.Define(context) - .WithEndpoint() - .AllowExceptions(e => e.Message.Contains("Simulated exception")) - .Done(c => c.MessageSentToError) - .Run(TimeSpan.FromMinutes(5)); - - Assert.AreEqual(3*3, context.TotalNumberOfFLRTimesInvokedInHandler); - Assert.AreEqual(3*3, context.TotalNumberOfFLRTimesInvoked); - Assert.AreEqual(2, context.NumberOfSLRRetriesPerformed); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - public int TotalNumberOfFLRTimesInvoked { get; set; } - public int TotalNumberOfFLRTimesInvokedInHandler { get; set; } - public int NumberOfSLRRetriesPerformed { get; set; } - public bool MessageSentToError { get; set; } - public Exception MessageSentToErrorException { get; set; } - } - - public class SLREndpoint : EndpointConfigurationBuilder - { - public SLREndpoint() - { - EndpointSetup() - .WithConfig(c => { c.MaxRetries = 3; }) - .WithConfig(c => - { - c.NumberOfRetries = 2; - c.TimeIncrease = TimeSpan.FromSeconds(1); - }); - } - - - class MessageToBeRetriedHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.Id != Context.Id) - { - return; // ignore messages from previous test runs - } - - Context.TotalNumberOfFLRTimesInvokedInHandler++; - - throw new MySpecialException(); - } - } - } - - public class MySpecialException : Exception - { - public MySpecialException() - : base("Simulated exception") - { - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid Id { get; set; } - } - - public class MyErrorSubscriber : IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - - public BusNotifications Notifications { get; set; } - - public IBus Bus { get; set; } - - public void Start() - { - unsubscribeStreams.Add(Notifications.Errors.MessageSentToErrorQueue.Subscribe(message => - { - Context.MessageSentToErrorException = message.Exception; - Context.MessageSentToError = true; - })); - unsubscribeStreams.Add(Notifications.Errors.MessageHasFailedAFirstLevelRetryAttempt.Subscribe(message => Context.TotalNumberOfFLRTimesInvoked++)); - unsubscribeStreams.Add(Notifications.Errors.MessageHasBeenSentToSecondLevelRetries.Subscribe(message => Context.NumberOfSLRRetriesPerformed++)); - - Bus.SendLocal(new MessageToBeRetried - { - Id = Context.Id - }); - } - - public void Stop() - { - foreach (var unsubscribeStream in unsubscribeStreams) - { - unsubscribeStream.Dispose(); - } - } - - List unsubscribeStreams = new List(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_default_settings.cs b/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_default_settings.cs deleted file mode 100644 index 30d9cbb07ba..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_default_settings.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_doing_flr_with_default_settings : NServiceBusAcceptanceTest - { - [Test] - public void Should_not_do_any_retries_if_transactions_are_off() - { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => - { - bus.SendLocal(new MessageToBeRetried { Id = context.Id }); - bus.SendLocal(new MessageToBeRetried { Id = context.Id, SecondMessage = true }); - })) - .AllowExceptions() - .Done(c => c.SecondMessageReceived || c.NumberOfTimesInvoked > 1) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.AreEqual(1, c.NumberOfTimesInvoked, "No retries should be in use if transactions are off")) - .Run(); - - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - - public int NumberOfTimesInvoked { get; set; } - - public bool HandedOverToSlr { get; set; } - - public bool SecondMessageReceived { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => - { - b.Transactions().Disable(); - b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); - }) - .WithConfig(c => c.MaximumConcurrencyLevel = 1); - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.HandedOverToSlr = true; - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.Id != Context.Id) return; // messages from previous test runs must be ignored - - if (message.SecondMessage) - { - Context.SecondMessageReceived = true; - return; - } - - Context.NumberOfTimesInvoked++; - - throw new Exception("Simulated exception"); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid Id { get; set; } - - public bool SecondMessage { get; set; } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_dtc_on.cs b/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_dtc_on.cs deleted file mode 100644 index a5ecd408cb9..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_dtc_on.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_doing_flr_with_dtc_on : NServiceBusAcceptanceTest - { - public static Func X = () => 5; - - [Test] - public void Should_do_X_retries_by_default_with_dtc_on() - { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => bus.SendLocal(new MessageToBeRetried{ Id = context.Id }))) - .AllowExceptions() - .Done(c => c.HandedOverToSlr || c.NumberOfTimesInvoked > X()) - .Repeat(r => r.For()) - .Should(c => Assert.AreEqual(X(), c.NumberOfTimesInvoked, string.Format("The FLR should by default retry {0} times", X()))) - .Run(); - - } - - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - - public int NumberOfTimesInvoked { get; set; } - - public bool HandedOverToSlr { get; set; } - - public bool SecondMessageReceived { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance))) - .WithConfig(c => c.MaximumConcurrencyLevel = 1); - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.HandedOverToSlr = true; - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.Id != Context.Id) return; // messages from previous test runs must be ignored - - if (message.SecondMessage) - { - Context.SecondMessageReceived = true; - return; - } - - Context.NumberOfTimesInvoked++; - - throw new Exception("Simulated exception"); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid Id { get; set; } - - public bool SecondMessage { get; set; } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_native_transactions.cs b/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_native_transactions.cs deleted file mode 100644 index 1eb09240799..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_doing_flr_with_native_transactions.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_doing_flr_with_native_transactions : NServiceBusAcceptanceTest - { - public static Func X = () => 5; - - - [Test] - public void Should_do_X_retries_by_default_with_native_transactions() - { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => bus.SendLocal(new MessageToBeRetried { Id = context.Id }))) - .AllowExceptions() - .Done(c => c.HandedOverToSlr || c.NumberOfTimesInvoked > X()) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.AreEqual(X(), c.NumberOfTimesInvoked, string.Format("The FLR should by default retry {0} times", X()))) - .Run(TimeSpan.FromMinutes(X())); - - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - - public int NumberOfTimesInvoked { get; set; } - - public bool HandedOverToSlr { get; set; } - - public bool SecondMessageReceived { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => - { - b.Transactions().DisableDistributedTransactions(); - b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); - }) - .WithConfig(c => c.MaximumConcurrencyLevel = 1); - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.HandedOverToSlr = true; - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.Id != Context.Id) return; // messages from previous test runs must be ignored - - if (message.SecondMessage) - { - Context.SecondMessageReceived = true; - return; - } - - Context.NumberOfTimesInvoked++; - - throw new Exception("Simulated exception"); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid Id { get; set; } - - public bool SecondMessage { get; set; } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_fails_flr.cs b/src/NServiceBus.AcceptanceTests/Retries/When_fails_flr.cs deleted file mode 100644 index 843724a8d3c..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_fails_flr.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - using ScenarioDescriptors; - - public class When_fails_flr : NServiceBusAcceptanceTest - { - static TimeSpan SlrDelay = TimeSpan.FromSeconds(5); - - [Test] - public void Should_be_moved_to_slr() - { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => b.Given((bus, context) => bus.SendLocal(new MessageToBeRetried { Id = context.Id }))) - .AllowExceptions(e => e.Message.Contains("Simulated exception")) - .Done(c => c.NumberOfTimesInvoked >= 2) - .Repeat(r => r.For(Transports.Default)) - .Should(context => - { - Assert.GreaterOrEqual(1,context.NumberOfSlrRetriesPerformed, "The SLR should only do one retry"); - Assert.GreaterOrEqual(context.TimeOfSecondAttempt - context.TimeOfFirstAttempt,SlrDelay , "The SLR should delay the retry"); - }) - .Run(); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - - public int NumberOfTimesInvoked { get; set; } - - public DateTime TimeOfFirstAttempt { get; set; } - public DateTime TimeOfSecondAttempt { get; set; } - - public int NumberOfSlrRetriesPerformed { get; set; } - } - - public class SLREndpoint : EndpointConfigurationBuilder - { - public SLREndpoint() - { - EndpointSetup() - .WithConfig(c => - { - c.MaxRetries = 0; //to skip the FLR - }) - .WithConfig(c => - { - c.NumberOfRetries = 1; - c.TimeIncrease = SlrDelay; - }); - } - - - class MessageToBeRetriedHandler:IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MessageToBeRetried message) - { - if (message.Id != Context.Id) return; // ignore messages from previous test runs - - Context.NumberOfTimesInvoked++; - - if (Context.NumberOfTimesInvoked == 1) - Context.TimeOfFirstAttempt = DateTime.UtcNow; - - if (Context.NumberOfTimesInvoked == 2) - { - Context.TimeOfSecondAttempt = DateTime.UtcNow; - } - - string retries; - - if (Bus.CurrentMessageContext.Headers.TryGetValue(Headers.Retries,out retries)) - { - Context.NumberOfSlrRetriesPerformed = int.Parse(retries); - } - - - throw new Exception("Simulated exception"); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - public Guid Id { get; set; } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_fails_with_retries_set_to_0.cs b/src/NServiceBus.AcceptanceTests/Retries/When_fails_with_retries_set_to_0.cs deleted file mode 100644 index deb31d45712..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_fails_with_retries_set_to_0.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using System.Collections.Generic; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using NServiceBus.Config; - using NUnit.Framework; - - public class When_fails_with_retries_set_to_0 : NServiceBusAcceptanceTest - { - [Test] - public void Should_not_retry_the_message_using_flr() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeRetried()))) - .AllowExceptions() - .Done(c => c.HandedOverToSlr) - .Run(); - - Assert.AreEqual(1, context.NumberOfTimesInvoked,"No FLR should be in use if MaxRetries is set to 0"); - } - - public class Context : ScenarioContext - { - public int NumberOfTimesInvoked { get; set; } - - public bool HandedOverToSlr { get; set; } - - public Dictionary HeadersOfTheFailedMessage { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance))) - .WithConfig(c => - { - c.MaxRetries = 0; - }); - } - - class CustomFaultManager: IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.HandedOverToSlr = true; - Context.HeadersOfTheFailedMessage = message.Headers; - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler:IHandleMessages - { - public Context Context { get; set; } - public void Handle(MessageToBeRetried message) - { - Context.NumberOfTimesInvoked++; - throw new Exception("Simulated exception"); - } - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Retries/When_sending_to_slr.cs b/src/NServiceBus.AcceptanceTests/Retries/When_sending_to_slr.cs deleted file mode 100644 index 538c0347520..00000000000 --- a/src/NServiceBus.AcceptanceTests/Retries/When_sending_to_slr.cs +++ /dev/null @@ -1,193 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Retries -{ - using System; - using System.Linq; - using Faults; - using EndpointTemplates; - using AcceptanceTesting; - using MessageMutator; - using NServiceBus.Config; - using NServiceBus.Unicast; - using NServiceBus.Unicast.Transport; - using NUnit.Framework; - using Unicast.Messages; - - public class When_sending_to_slr : NServiceBusAcceptanceTest - { - [Test] - public void Should_preserve_the_original_body_for_regular_exceptions() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeRetried()))) - .AllowExceptions() - .Done(c => c.SlrChecksum != default(byte)) - .Run(); - - Assert.AreEqual(context.OriginalBodyChecksum, context.SlrChecksum, "The body of the message sent to slr should be the same as the original message coming off the queue"); - - } - - [Test] - public void Should_raise_FinishedMessageProcessing_event() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeRetried()))) - .Done(c => c.FinishedMessageProcessingCalledAfterFaultManagerInvoked) - .Run(); - - Assert.IsTrue(context.FinishedMessageProcessingCalledAfterFaultManagerInvoked); - - } - - [Test] - public void Should_preserve_the_original_body_for_serialization_exceptions() - { - var context = new Context - { - SimulateSerializationException = true - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MessageToBeRetried()))) - .AllowExceptions() - .Done(c => c.SlrChecksum != default(byte)) - .Run(); - - Assert.AreEqual(context.OriginalBodyChecksum, context.SlrChecksum, "The body of the message sent to slr should be the same as the original message coming off the queue"); - - } - - public class Context : ScenarioContext - { - public bool FinishedMessageProcessingCalledAfterFaultManagerInvoked { get; set; } - public bool FaultManagerInvoked { get; set; } - - public byte OriginalBodyChecksum { get; set; } - - public byte SlrChecksum { get; set; } - - public bool SimulateSerializationException { get; set; } - } - - public class RetryEndpoint : EndpointConfigurationBuilder - { - public RetryEndpoint() - { - EndpointSetup( - b => b.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance))) - .WithConfig(c => - { - c.MaxRetries = 0; - }); - } - - class FinishedProcessingListener : IWantToRunWhenBusStartsAndStops - { - readonly Context context; - - public FinishedProcessingListener(UnicastBus bus, Context context) - { - this.context = context; - bus.Transport.FinishedMessageProcessing += Transport_FinishedMessageProcessing; - } - - void Transport_FinishedMessageProcessing(object sender, FinishedMessageProcessingEventArgs e) - { - if (context.FaultManagerInvoked) - { - context.FinishedMessageProcessingCalledAfterFaultManagerInvoked = true; - } - } - - public void Start() - { - } - - public void Stop() - { - } - } - - class BodyMutator : IMutateTransportMessages, INeedInitialization - { - public Context Context { get; set; } - - public void MutateIncoming(TransportMessage transportMessage) - { - - var originalBody = transportMessage.Body; - - Context.OriginalBodyChecksum = Checksum(originalBody); - - var decryptedBody = new byte[originalBody.Length]; - - Buffer.BlockCopy(originalBody,0,decryptedBody,0,originalBody.Length); - - //decrypt - decryptedBody[0]++; - - if (Context.SimulateSerializationException) - decryptedBody[1]++; - - transportMessage.Body = decryptedBody; - } - - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - transportMessage.Body[0]--; - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } - - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - Context.FaultManagerInvoked = true; - Context.SlrChecksum = Checksum(message.Body); - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.FaultManagerInvoked = true; - Context.SlrChecksum = Checksum(message.Body); - } - - public void Init(Address address) - { - - } - } - - class MessageToBeRetriedHandler : IHandleMessages - { - public void Handle(MessageToBeRetried message) - { - throw new Exception("Simulated exception"); - } - } - - public static byte Checksum(byte[] data) - { - var longSum = data.Sum(x => (long)x); - return unchecked((byte)longSum); - } - } - - [Serializable] - public class MessageToBeRetried : IMessage - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga.cs b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga.cs new file mode 100644 index 00000000000..da10612a1ea --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga.cs @@ -0,0 +1,114 @@ +namespace NServiceBus.AcceptanceTests.Routing.AutomaticSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + [TestFixture] + public class When_starting_an_endpoint_with_a_saga : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_autoSubscribe_the_saga_messageHandler_by_default() + { + var context = await Scenario.Define() + .WithEndpoint() + .Done(c => c.EventsSubscribedTo.Count >= 2) + .Run(); + + Assert.True(context.EventsSubscribedTo.Contains(typeof(MyEvent)), "Events only handled by sagas should be auto subscribed"); + Assert.True(context.EventsSubscribedTo.Contains(typeof(MyEventBase)), "Sagas should be auto subscribed even when handling a base class event"); + } + + class Context : ScenarioContext + { + public Context() + { + EventsSubscribedTo = new List(); + } + + public List EventsSubscribedTo { get; } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.Pipeline.Register("SubscriptionSpy", new SubscriptionSpy((Context)ScenarioContext), "Spies on subscriptions made")) + .AddMapping(typeof(Subscriber)) //just map to our self for this test + .AddMapping(typeof(Subscriber)); //just map to our self for this test + } + + class SubscriptionSpy : IBehavior + { + public SubscriptionSpy(Context testContext) + { + this.testContext = testContext; + } + + public async Task Invoke(ISubscribeContext context, Func next) + { + await next(context).ConfigureAwait(false); + + testContext.EventsSubscribedTo.Add(context.EventType); + } + + Context testContext; + } + + public class AutoSubscriptionSaga : Saga, IAmStartedByMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + } + + public class AutoSubscriptionSagaData : ContainSagaData + { + public virtual string SomeId { get; set; } + } + } + + public class MySagaThatReactsOnASuperClassEvent : Saga, + IAmStartedByMessages + { + public Task Handle(MyEventBase message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + } + + public class SuperClassEventSagaData : ContainSagaData + { + public virtual string SomeId { get; set; } + } + } + } + + public class MyEventBase : IEvent + { + public string SomeId { get; set; } + } + + public class MyEventWithParent : MyEventBase + { + } + + public class MyEvent : IEvent + { + public string SomeId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga_autosubscribe_disabled.cs b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga_autosubscribe_disabled.cs new file mode 100644 index 00000000000..05e829bf323 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_a_saga_autosubscribe_disabled.cs @@ -0,0 +1,118 @@ +namespace NServiceBus.AcceptanceTests.Routing.AutomaticSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + + [TestFixture] + public class When_starting_an_endpoint_with_a_saga_autosubscribe_disabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_autoSubscribe_messages_handled_by_sagas_if_asked_to() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.CustomConfig(c => c.AutoSubscribe().DoNotAutoSubscribeSagas())) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.False(context.EventsSubscribedTo.Any(), "Events only handled by sagas should not be auto subscribed"); + } + + class Context : ScenarioContext + { + public Context() + { + EventsSubscribedTo = new List(); + } + + public List EventsSubscribedTo { get; } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.Pipeline.Register("SubscriptionSpy", new SubscriptionSpy((Context)ScenarioContext), "Spies on subscriptions made")) + .AddMapping(typeof(Subscriber)) //just map to our self for this test + .AddMapping(typeof(Subscriber)); //just map to our self for this test + } + + class SubscriptionSpy : IBehavior + { + public SubscriptionSpy(Context testContext) + { + this.testContext = testContext; + } + + public async Task Invoke(ISubscribeContext context, Func next) + { + await next(context).ConfigureAwait(false); + + testContext.EventsSubscribedTo.Add(context.EventType); + } + + Context testContext; + } + + public class NotAutoSubscribedSaga : Saga, IAmStartedByMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + } + + public class NotAutoSubscribedSagaSagaData : ContainSagaData + { + public virtual string SomeId { get; set; } + } + } + + public class NotAutoSubsubscribedSagaThatReactsOnASuperClassEvent : Saga, + IAmStartedByMessages + { + public Task Handle(MyEventBase message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(saga => saga.SomeId).ToSaga(saga => saga.SomeId); + } + + public class NotAutosubscribeSuperClassEventSagaData : ContainSagaData + { + public virtual string SomeId { get; set; } + } + } + } + + public class MyEventBase : IEvent + { + public string SomeId { get; set; } + } + + public class MyEventWithParent : MyEventBase + { + } + + public class MyMessage : IMessage + { + } + + public class MyEvent : IEvent + { + public string SomeId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_autoSubscribe.cs b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_autoSubscribe.cs new file mode 100644 index 00000000000..1645c02b3dc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/AutomaticSubscriptions/When_starting_an_endpoint_with_autoSubscribe.cs @@ -0,0 +1,121 @@ +namespace NServiceBus.AcceptanceTests.Routing.AutomaticSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + [TestFixture] + public class When_starting_an_endpoint_with_autosubscribe : NServiceBusAcceptanceTest + { + [Test] + public Task Should_autosubscribe_to_relevant_messagetypes() + { + return Scenario.Define() + .WithEndpoint() + .Done(c => c.EventsSubscribedTo.Count >= 1) + .Repeat(b => b.For()) + .Should(ctx => Assert.True(ctx.EventsSubscribedTo.Contains(typeof(MyEvent)), "Events should be auto subscribed")) + .Should(ctx => Assert.False(ctx.EventsSubscribedTo.Contains(typeof(MyEventWithNoRouting)), "Events without routing should not be auto subscribed")) + .Should(ctx => Assert.False(ctx.EventsSubscribedTo.Contains(typeof(MyEventWithNoHandler)), "Events without handlers should not be auto subscribed")) + .Should(ctx => Assert.False(ctx.EventsSubscribedTo.Contains(typeof(MyCommand)), "Commands should not be auto subscribed")) + .Should(ctx => Assert.False(ctx.EventsSubscribedTo.Contains(typeof(MyMessage)), "Plain messages should not be auto subscribed by default")) + .Run(); + } + + class Context : ScenarioContext + { + public Context() + { + EventsSubscribedTo = new List(); + } + + public List EventsSubscribedTo { get; } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.Pipeline.Register("SubscriptionSpy", new SubscriptionSpy((Context)ScenarioContext), "Spies on subscriptions made")) + .AddMapping(typeof(Subscriber)) //just map to our self for this test + .AddMapping(typeof(Subscriber)) //just map to our self for this test + .AddMapping(typeof(Subscriber)) //just map to our self for this test + .AddMapping(typeof(Subscriber)); //just map to our self for this test + } + + class SubscriptionSpy : IBehavior + { + public SubscriptionSpy(Context testContext) + { + this.testContext = testContext; + } + + public async Task Invoke(ISubscribeContext context, Func next) + { + await next(context).ConfigureAwait(false); + + testContext.EventsSubscribedTo.Add(context.EventType); + } + + Context testContext; + } + + class MyMessageHandler : IHandleMessages + { + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + + public class EventMessageHandler : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + + public class MyEventWithNoRoutingHandler : IHandleMessages + { + public Task Handle(MyEventWithNoRouting message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + + public class CommandMessageHandler : IHandleMessages + { + public Task Handle(MyCommand message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + class MyMessage : IMessage + { + } + + class MyCommand : ICommand + { + } + + class MyEvent : IEvent + { + } + + class MyEventWithNoRouting : IEvent + { + } + + class MyEventWithNoHandler : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_extending_event_routing.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_extending_event_routing.cs new file mode 100644 index 00000000000..a3232a5bcec --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_extending_event_routing.cs @@ -0,0 +1,77 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using Features; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_extending_event_routing : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + + [Test] + public async Task Should_route_events_correctly() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, session => session.Publish()) + ) + .WithEndpoint(b => b.When(async (session, context) => { await session.Subscribe(); })) + .Done(c => c.MessageDelivered) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.MessageDelivered)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool MessageDelivered { get; set; } + public bool Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; })); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + c.GetSettings().GetOrCreate() + .AddOrReplacePublishers("CustomRoutingFeature", new List + { + new PublisherTableEntry(typeof(MyEvent), PublisherAddress.CreateFromEndpointName(PublisherEndpoint)) + }); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent evnt, IMessageHandlerContext context) + { + Context.MessageDelivered = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_multi_subscribing_to_a_polymorphic_event.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_multi_subscribing_to_a_polymorphic_event.cs new file mode 100644 index 00000000000..5f30ef6f335 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_multi_subscribing_to_a_polymorphic_event.cs @@ -0,0 +1,134 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_multi_subscribing_to_a_polymorphic_event : NServiceBusAcceptanceTest + { + [Test] + public async Task Both_events_should_be_delivered() + { + await Scenario.Define() + .WithEndpoint(b => b.When(c => c.Publisher1HasASubscriberForIMyEvent, (session, c) => + { + c.AddTrace("Publishing MyEvent1"); + return session.Publish(new MyEvent1()); + })) + .WithEndpoint(b => b.When(c => c.Publisher2HasDetectedASubscriberForEvent2, (session, c) => + { + c.AddTrace("Publishing MyEvent2"); + return session.Publish(new MyEvent2()); + })) + .WithEndpoint(b => b.When(async (session, c) => + { + c.AddTrace("Subscriber1 subscribing to both events"); + await session.Subscribe(); + await session.Subscribe(); + })) + .Done(c => c.SubscriberGotIMyEvent && c.SubscriberGotMyEvent2) + .Repeat(r => r.For()) + .Should(c => + { + Assert.True(c.SubscriberGotIMyEvent); + Assert.True(c.SubscriberGotMyEvent2); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotIMyEvent { get; set; } + public bool SubscriberGotMyEvent2 { get; set; } + public bool Publisher1HasASubscriberForIMyEvent { get; set; } + public bool Publisher2HasDetectedASubscriberForEvent2 { get; set; } + } + + public class Publisher1 : EndpointConfigurationBuilder + { + public Publisher1() + { + EndpointSetup(b => + { + //Immediate Retries on since subscription storages can throw on concurrency violation and need to retry + b.Recoverability().Immediate(immediate => immediate.NumberOfRetries(5)); + b.OnEndpointSubscribed((args, context) => + { + context.AddTrace("Publisher1 OnEndpointSubscribed " + args.MessageType); + if (args.MessageType.Contains(typeof(IMyEvent).Name)) + { + context.Publisher1HasASubscriberForIMyEvent = true; + } + }); + }); + } + } + + public class Publisher2 : EndpointConfigurationBuilder + { + public Publisher2() + { + EndpointSetup(b => + { + // Immediate Retries on since subscription storages can throw on concurrency violation and need to retry + b.Recoverability().Immediate(immediate => immediate.NumberOfRetries(5)); + + b.OnEndpointSubscribed((args, context) => + { + context.AddTrace("Publisher2 OnEndpointSubscribed " + args.MessageType); + + if (args.MessageType.Contains(typeof(MyEvent2).Name)) + { + context.Publisher2HasDetectedASubscriberForEvent2 = true; + } + }); + }); + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher1)) + .AddMapping(typeof(Publisher2)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IMyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.AddTrace($"Got event '{messageThatIsEnlisted}'"); + if (messageThatIsEnlisted is MyEvent2) + { + Context.SubscriberGotMyEvent2 = true; + } + else + { + Context.SubscriberGotIMyEvent = true; + } + + return Task.FromResult(0); + } + } + } + + public class MyEvent1 : IMyEvent + { + } + + public class MyEvent2 : IMyEvent + { + } + + public interface IMyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_from_sendonly.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_from_sendonly.cs new file mode 100644 index 00000000000..aff5d083669 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_from_sendonly.cs @@ -0,0 +1,119 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NUnit.Framework; + using Persistence; + using ScenarioDescriptors; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + public class When_publishing_from_sendonly : NServiceBusAcceptanceTest + { + [Test] + public Task Should_be_delivered_to_all_subscribers() + { + return Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Publish(new MyEvent()))) + .WithEndpoint() + .Done(c => c.SubscriberGotTheEvent) + .Repeat(r => r.For()) + .Should(ctx => Assert.True(ctx.SubscriberGotTheEvent)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotTheEvent { get; set; } + } + + public class SendOnlyPublisher : EndpointConfigurationBuilder + { + public SendOnlyPublisher() + { + EndpointSetup(b => + { + b.UsePersistence(typeof(HardCodedPersistence)); + b.DisableFeature(); + }).SendOnly(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(SendOnlyPublisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.SubscriberGotTheEvent = true; + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyEvent : IEvent + { + } + + public class HardCodedPersistence : PersistenceDefinition + { + internal HardCodedPersistence() + { + Supports(s => s.EnableFeatureByDefault()); + } + } + + public class HardCodedPersistenceFeature : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + } + } + + public class HardcodedSubscriptionManager : ISubscriptionStorage + { + public HardcodedSubscriptionManager() + { + addressTask = Task.FromResult(new[] + { + new Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber("publishingFromSendonly.subscriber", null) + }.AsEnumerable()); + } + + public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context) + { + return Task.FromResult(0); + } + + public Task Unsubscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context) + { + return Task.FromResult(0); + } + + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) + { + return addressTask; + } + + Task> addressTask; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_to_scaled_out_subscribers.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_to_scaled_out_subscribers.cs new file mode 100644 index 00000000000..797f14ac2a9 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_publishing_to_scaled_out_subscribers.cs @@ -0,0 +1,118 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_to_scaled_out_subscribers : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + + [Test] + public async Task Each_event_should_be_delivered_to_single_instance_of_each_subscriber() + { + await Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscribersCounter == 4, async (session, c) => { await session.Publish(new MyEvent()); })) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .Done(c => c.ProcessedByA > 0 && c.ProcessedByB > 0) + .Repeat(r => r.For()) + .Should(c => + { + Assert.AreEqual(1, c.ProcessedByA); + Assert.AreEqual(1, c.ProcessedByB); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public int SubscribersCounter => subscribersCounter; + + public int ProcessedByA => processedByA; + + public int ProcessedByB => processedByB; + + public void IncrementA() + { + Interlocked.Increment(ref processedByA); + } + + public void IncrementB() + { + Interlocked.Increment(ref processedByB); + } + + public void IncrementSubscribersCounter() + { + Interlocked.Increment(ref subscribersCounter); + } + + int processedByA; + int processedByB; + int subscribersCounter; + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => { c.OnEndpointSubscribed((s, context) => { context.IncrementSubscribersCounter(); }); }); + } + } + + public class SubscriberA : EndpointConfigurationBuilder + { + public SubscriberA() + { + EndpointSetup(c => + { + c.MessageDrivenPubSubRouting().RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.IncrementA(); + return Task.FromResult(0); + } + } + } + + public class SubscriberB : EndpointConfigurationBuilder + { + public SubscriberB() + { + EndpointSetup(c => + { + c.MessageDrivenPubSubRouting().RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.IncrementB(); + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event.cs new file mode 100644 index 00000000000..73d4f236c13 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event.cs @@ -0,0 +1,88 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_a_base_event : NServiceBusAcceptanceTest + { + [Test] + public Task Both_base_and_specific_events_should_be_delivered() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscriberSubscribed, async session => + { + await session.Publish(new SpecificEvent()); + await session.Publish(); + })) + .WithEndpoint(b => b.When(async (session, c) => await session.Subscribe())) + .Done(c => c.SubscriberGotBaseEvent && c.SubscriberGotSpecificEvent) + .Repeat(r => r.For()) + .Should(c => + { + Assert.True(c.SubscriberGotBaseEvent); + Assert.True(c.SubscriberGotSpecificEvent); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotBaseEvent { get; set; } + public bool SubscriberGotSpecificEvent { get; set; } + public bool SubscriberSubscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribed = true; + })); + } + } + + public class GeneralSubscriber : EndpointConfigurationBuilder + { + public GeneralSubscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IBaseEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + if (messageThatIsEnlisted is SpecificEvent) + { + Context.SubscriberGotSpecificEvent = true; + } + else + { + Context.SubscriberGotBaseEvent = true; + } + return Task.FromResult(0); + } + } + } + + public class SpecificEvent : IBaseEvent + { + } + + public interface IBaseEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_a_route_for_a_derived_event.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_a_route_for_a_derived_event.cs new file mode 100644 index 00000000000..7642fc41892 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_a_route_for_a_derived_event.cs @@ -0,0 +1,105 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_a_base_event_with_a_route_for_a_derived_event : NServiceBusAcceptanceTest + { + [Test] + public Task Event_should_be_delivered() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscriberSubscribedToOne, async session => + { + await session.Publish(new EventOne()); + })) + .WithEndpoint(b => b.When(c => c.SubscriberSubscribedToTwo, async session => + { + await session.Publish(new EventTwo()); + })) + .WithEndpoint(b => b.When(async (session, c) => await session.Subscribe())) + .Done(c => c.SubscriberGotEventOne) + .Repeat(r => r.For()) + .Should(c => Assert.IsTrue(c.SubscriberGotEventOne)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotEventOne { get; set; } + public bool SubscriberGotEventTwo { get; set; } + + public bool SubscriberSubscribedToOne { get; set; } + public bool SubscriberSubscribedToTwo { get; set; } + } + + public class PublisherOne : EndpointConfigurationBuilder + { + public PublisherOne() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribedToOne = true; + })); + } + } + + public class PublisherTwo : EndpointConfigurationBuilder + { + public PublisherTwo() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribedToTwo = true; + })); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + }) + .AddMapping(typeof(PublisherOne)) + .AddMapping(typeof(PublisherTwo)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IBaseEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + if (messageThatIsEnlisted is EventOne) + { + Context.SubscriberGotEventOne = true; + } + if (messageThatIsEnlisted is EventTwo) + { + Context.SubscriberGotEventTwo = true; + } + return Task.FromResult(0); + } + } + } + + public class EventOne : IBaseEvent + { + } + + public class EventTwo : IBaseEvent + { + } + + public interface IBaseEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_routes_to_base_and_specific_events.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_routes_to_base_and_specific_events.cs new file mode 100644 index 00000000000..acd08db2cc1 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_base_event_with_routes_to_base_and_specific_events.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_a_base_event_with_routes_to_base_and_specific_events : NServiceBusAcceptanceTest + { + [Test] + public Task Event_from_both_publishers_should_be_delivered() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscriberSubscribedToOne, async session => + { + await session.Publish(new EventOne()); + })) + .WithEndpoint(b => b.When(c => c.SubscriberSubscribedToTwo, async session => + { + await session.Publish(new EventTwo()); + })) + .WithEndpoint(b => b.When(async (session, c) => await session.Subscribe())) + .Done(c => c.SubscriberGotEventOne) + .Repeat(r => r.For()) + .Should(c => Assert.IsTrue(c.SubscriberGotEventOne)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotEventOne { get; set; } + public bool SubscriberSubscribedToOne { get; set; } + public bool SubscriberSubscribedToTwo { get; set; } + } + + public class PublisherOne : EndpointConfigurationBuilder + { + public PublisherOne() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribedToOne = true; + })); + } + } + + public class PublisherTwo : EndpointConfigurationBuilder + { + public PublisherTwo() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribedToTwo = true; + })); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + }) + .AddMapping(typeof(PublisherTwo)) + .AddMapping(typeof(PublisherOne)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IBaseEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + if (messageThatIsEnlisted is EventOne) + { + Context.SubscriberGotEventOne = true; + } + return Task.FromResult(0); + } + } + } + + public class EventOne : IBaseEvent + { + } + + public class EventTwo : IBaseEvent + { + } + + public interface IBaseEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_derived_event.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_derived_event.cs new file mode 100644 index 00000000000..3435001e020 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_a_derived_event.cs @@ -0,0 +1,96 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_a_derived_event : NServiceBusAcceptanceTest + { + [Test] + public Task Base_event_should_not_be_delivered() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscriberSubscribed, async session => + { + await session.Publish(); + await session.Send(new Done()); + })) + .WithEndpoint(b => b.When(async (session, c) => await session.Subscribe())) + .Done(c => c.Done) + .Repeat(r => r.For()) + .Should(c => Assert.IsFalse(c.SubscriberGotEvent)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotEvent { get; set; } + + public bool SubscriberSubscribed { get; set; } + + public bool Done { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((args, context) => + { + context.SubscriberSubscribed = true; + })) + .AddMapping(typeof(Subscriber)); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + c.LimitMessageProcessingConcurrencyTo(1); //To ensure Done is processed after the event. + }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(SpecificEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.SubscriberGotEvent = true; + return Task.FromResult(0); + } + } + + public class DoneHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Done message, IMessageHandlerContext context) + { + Context.Done = true; + return Task.FromResult(0); + } + } + } + + public class SpecificEvent : IBaseEvent + { + } + + public interface IBaseEvent : IEvent + { + } + + public class Done : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_multiple_publishers.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_multiple_publishers.cs new file mode 100644 index 00000000000..40b0f989dad --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_multiple_publishers.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_multiple_publishers : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_subscribe_to_all_registered_publishers_of_same_type() + { + await Scenario.Define() + .WithEndpoint(e => e + .When(s => s.Subscribe())) + .WithEndpoint(e => e + .CustomConfig(cfg => + { + cfg.OverrideLocalAddress("Publisher1"); + cfg.OnEndpointSubscribed((args, ctx) => ctx.SubscribedToPublisher1 = true); + })) + .WithEndpoint(e => e + .CustomConfig(cfg => + { + cfg.OverrideLocalAddress("Publisher2"); + cfg.OnEndpointSubscribed((args, ctx) => ctx.SubscribedToPublisher2 = true); + })) + .Done(c => c.SubscribedToPublisher1 && c.SubscribedToPublisher2) + .Repeat(r => r.For()) + .Should(c => + { + Assert.That(c.SubscribedToPublisher1, Is.True); + Assert.That(c.SubscribedToPublisher2, Is.True); + }) + .Run(); + } + + class Context : ScenarioContext + { + public bool SubscribedToPublisher1 { get; set; } + public bool SubscribedToPublisher2 { get; set; } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + var routing = c.MessageDrivenPubSubRouting(); + routing.RegisterPublisher(typeof(SomeEvent).Assembly, "Publisher1"); + routing.RegisterPublisher(typeof(SomeEvent), "Publisher2"); + }); + } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(); + } + } + + class SomeEvent : IEvent + { + } + } +} diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_scaled_out_publisher.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_scaled_out_publisher.cs new file mode 100644 index 00000000000..b9fcc83ac86 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_subscribing_to_scaled_out_publisher.cs @@ -0,0 +1,69 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Routing; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_subscribing_to_scaled_out_publisher : NServiceBusAcceptanceTest + { + [Test] + public Task Should_send_subscription_message_to_each_instance() + { + return Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .WithEndpoint(b => b.When(s => s.Subscribe())) + .Done(c => c.PublisherReceivedSubscription.Count >= 2) + .Repeat(r => r.For()) + .Should(c => + { + // each instance should receive a subscription message + Assert.That(c.PublisherReceivedSubscription, Does.Contain("1")); + Assert.That(c.PublisherReceivedSubscription, Does.Contain("2")); + Assert.That(c.PublisherReceivedSubscription.Count, Is.EqualTo(2)); + }) + .Run(); + } + + class Context : ScenarioContext + { + public List PublisherReceivedSubscription { get; } = new List(); + } + + class ScaledOutPublisher : EndpointConfigurationBuilder + { + public ScaledOutPublisher() + { + // store the instance discriminator of each instance receiving a subscription message: + EndpointSetup(c => c + .OnEndpointSubscribed((subscription, context) => + context.PublisherReceivedSubscription.Add(c.GetSettings().Get("EndpointInstanceDiscriminator")))); + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup((c, r) => + { + // configure the scaled out publisher instances: + var publisherName = Conventions.EndpointNamingConvention(typeof(ScaledOutPublisher)); + var routing = c.UseTransport(r.GetTransportType()).Routing(); + c.MessageDrivenPubSubRouting().RegisterPublisher(typeof(MyEvent), publisherName); + routing.RegisterEndpointInstances(new EndpointInstance(publisherName, "1"), new EndpointInstance(publisherName, "2")); + }); + } + } + + class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_unsubscribing_to_scaled_out_publisher.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_unsubscribing_to_scaled_out_publisher.cs new file mode 100644 index 00000000000..7220f8b551f --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_unsubscribing_to_scaled_out_publisher.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Routing; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_unsubscribing_to_scaled_out_publisher : NServiceBusAcceptanceTest + { + [Test] + public Task Should_send_unsubscribe_message_to_each_instance() + { + return Scenario.Define() + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .WithEndpoint(b => b.When(s => s.Unsubscribe())) + .Done(c => c.PublisherReceivedUnsubscribeMessage.Count >= 2) + .Repeat(r => r.For()) + .Should(c => + { + // each instance should receive an unsubscribe message + Assert.That(c.PublisherReceivedUnsubscribeMessage, Does.Contain("1")); + Assert.That(c.PublisherReceivedUnsubscribeMessage, Does.Contain("2")); + Assert.That(c.PublisherReceivedUnsubscribeMessage.Count, Is.EqualTo(2)); + }) + .Run(); + } + + class Context : ScenarioContext + { + public List PublisherReceivedUnsubscribeMessage { get; } = new List(); + } + + class ScaledOutPublisher : EndpointConfigurationBuilder + { + public ScaledOutPublisher() + { + // store the instance discriminator of each instance receiving a unsubscribe message: + EndpointSetup(c => + { + c.OnEndpointUnsubscribed((subscription, context) => + context.PublisherReceivedUnsubscribeMessage.Add(c.GetSettings().Get("EndpointInstanceDiscriminator"))); + }); + } + } + + class Unsubscriber : EndpointConfigurationBuilder + { + public Unsubscriber() + { + EndpointSetup((c, r) => + { + // configure the scaled out publisher instances: + var publisherName = Conventions.EndpointNamingConvention(typeof(ScaledOutPublisher)); + var routing = c.UseTransport(r.GetTransportType()).Routing(); + c.MessageDrivenPubSubRouting().RegisterPublisher(typeof(MyEvent), publisherName); + routing.RegisterEndpointInstances(new EndpointInstance(publisherName, "1"), new EndpointInstance(publisherName, "2")); + }); + } + } + + class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_assembly_level_message_mapping_for_pub_sub.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_assembly_level_message_mapping_for_pub_sub.cs new file mode 100644 index 00000000000..c6a52d172be --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_assembly_level_message_mapping_for_pub_sub.cs @@ -0,0 +1,115 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using Features; + using NServiceBus.Config; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_using_assembly_level_message_mapping_for_pub_sub : NServiceBusAcceptanceTest + { + static string OtherEndpointName => Conventions.EndpointNamingConvention(typeof(OtherEndpoint)); + + [Test] + public async Task The_mapping_should_not_cause_publishing_to_non_subscribers() + { + await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => + b.When(c => c.EndpointsStarted, async session => + { + await session.Publish(new MyEvent()); + await session.Send(new DoneCommand()); + }) + ) + .Done(c => c.CommandReceived || c.EventReceived) + .Repeat(r => r.For()) + .Should(c => + { + Assert.IsFalse(c.EventReceived); + Assert.IsTrue(c.CommandReceived); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool EventReceived { get; set; } + public bool CommandReceived { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup() + .WithConfig(c => + { + c.MessageEndpointMappings = new MessageEndpointMappingCollection(); + c.MessageEndpointMappings.Add(new MessageEndpointMapping + { + Endpoint = OtherEndpointName, + AssemblyName = typeof(Publisher).Assembly.GetName().Name + }); + }) + .AddMapping(typeof(OtherEndpoint)); + } + } + + public class OtherEndpoint : EndpointConfigurationBuilder + { + public OtherEndpoint() + { + EndpointSetup(c => + { + // do not subscribe to the event since we don't want to receive it. + c.DisableFeature(); + }); + } + + public class EventHandler : IHandleMessages + { + Context testContext; + + public EventHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + testContext.EventReceived = true; + return Task.FromResult(0); + } + } + + public class DoneHandler : IHandleMessages + { + Context testContext; + + public DoneHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(DoneCommand message, IMessageHandlerContext context) + { + testContext.CommandReceived = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + + public class DoneCommand : ICommand + { + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_autosubscribe_with_missing_routing_information.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_autosubscribe_with_missing_routing_information.cs new file mode 100644 index 00000000000..6ff8af860b2 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_autosubscribe_with_missing_routing_information.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_using_autosubscribe_with_missing_routing_information : NServiceBusAcceptanceTest + { + [Test] + public Task Should_skip_events_with_missing_routes() + { + return Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Repeat(r => r.For()) + .Should(c => { Assert.True(c.EndpointsStarted); }) + .Run(); + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_legacy_routing_configuration.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_legacy_routing_configuration.cs new file mode 100644 index 00000000000..79d433af6c5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/When_using_legacy_routing_configuration.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_using_legacy_routing_configuration : NServiceBusAcceptanceTest + { + [Test] + public async Task Events_routes_and_command_routes_should_be_kept_separate() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, async session => + { + await session.Publish(new MyEvent()); + await session.Send(new DoneCommand()); + }) + ) + .WithEndpoint(b => b.When((session, context) => session.Subscribe())) + .Done(c => c.Done) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.HandlerInvoked == 1)) + .Run(); + } + + public class Context : ScenarioContext + { + public int HandlerInvoked { get; set; } + public bool Subscribed { get; set; } + public bool Done { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; })) + .AddMapping(typeof(Subscriber)) + .AddMapping(typeof(Subscriber)); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + c.LimitMessageProcessingConcurrencyTo(1); + }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.HandlerInvoked++; + return Task.FromResult(0); + } + } + + public class DoneHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(DoneCommand message, IMessageHandlerContext context) + { + Context.Done = true; + return Task.FromResult(0); + } + } + } + + public class DoneCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_multi_subscribing_to_a_polymorphic_event.cs b/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_multi_subscribing_to_a_polymorphic_event.cs new file mode 100644 index 00000000000..cc415963949 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_multi_subscribing_to_a_polymorphic_event.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.AcceptanceTests.Routing.NativePublishSubscribe +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_multi_subscribing_to_a_polymorphic_event : NServiceBusAcceptanceTest + { + [Test] + public Task Both_events_should_be_delivered() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.EndpointsStarted, (session, c) => + { + c.AddTrace("Publishing MyEvent1"); + return session.Publish(new MyEvent1()); + })) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, (session, c) => + { + c.AddTrace("Publishing MyEvent2"); + return session.Publish(new MyEvent2()); + })) + .WithEndpoint() + .Done(c => c.SubscriberGotIMyEvent && c.SubscriberGotMyEvent2) + .Repeat(r => r.For()) + .Should(c => + { + Assert.True(c.SubscriberGotIMyEvent); + Assert.True(c.SubscriberGotMyEvent2); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SubscriberGotIMyEvent { get; set; } + public bool SubscriberGotMyEvent2 { get; set; } + } + + public class Publisher1 : EndpointConfigurationBuilder + { + public Publisher1() + { + EndpointSetup(); + } + } + + public class Publisher2 : EndpointConfigurationBuilder + { + public Publisher2() + { + EndpointSetup(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IMyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.AddTrace($"Got event '{messageThatIsEnlisted}'"); + if (messageThatIsEnlisted is MyEvent2) + { + Context.SubscriberGotMyEvent2 = true; + } + else + { + Context.SubscriberGotIMyEvent = true; + } + + return Task.FromResult(0); + } + } + } + + public class MyEvent1 : IMyEvent + { + } + + public class MyEvent2 : IMyEvent + { + } + + public interface IMyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_publishing_to_scaled_out_subscribers.cs b/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_publishing_to_scaled_out_subscribers.cs new file mode 100644 index 00000000000..ab0a9302f87 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/NativePublishSubscribe/When_publishing_to_scaled_out_subscribers.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.AcceptanceTests.Routing.NativePublishSubscribe +{ + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_to_scaled_out_subscribers : NServiceBusAcceptanceTest + { + [Test] + public async Task Each_event_should_be_delivered_to_single_instance_of_each_subscriber() + { + await Scenario.Define() + .WithEndpoint(b => b.When(c => c.EndpointsStarted, async (session, c) => { await session.Publish(new MyEvent()); })) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(b => b.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .Done(c => c.SubscriberACounter > 0 && c.SubscriberBCounter > 0) + .Repeat(r => r.For()) + .Should(c => + { + Assert.IsTrue(c.SubscriberACounter == 1); + Assert.IsTrue(c.SubscriberBCounter == 1); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public int SubscriberACounter => subscriberACounter; + + public int SubscriberBCounter => subscriberBCounter; + + public void IncrementSubscriberACounter() + { + Interlocked.Increment(ref subscriberACounter); + } + + public void IncrementSubscriberBCounter() + { + Interlocked.Increment(ref subscriberBCounter); + } + + int subscriberACounter; + int subscriberBCounter; + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(); + } + } + + public class SubscriberA : EndpointConfigurationBuilder + { + public SubscriberA() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.IncrementSubscriberACounter(); + return Task.FromResult(0); + } + } + } + + public class SubscriberB : EndpointConfigurationBuilder + { + public SubscriberB() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.IncrementSubscriberBCounter(); + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehavior.cs b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehavior.cs new file mode 100644 index 00000000000..e54a6360d70 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehavior.cs @@ -0,0 +1,64 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using NServiceBus.Pipeline; + using ObjectBuilder; + using Transport; + + class SubscriptionBehavior : IBehavior where TContext : ScenarioContext + { + public SubscriptionBehavior(Action action, TContext scenarioContext, MessageIntentEnum intentToHandle) + { + this.action = action; + this.scenarioContext = scenarioContext; + this.intentToHandle = intentToHandle; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + await next(context).ConfigureAwait(false); + var subscriptionMessageType = GetSubscriptionMessageTypeFrom(context.Message); + if (subscriptionMessageType != null) + { + string returnAddress; + if (!context.Message.Headers.TryGetValue(Headers.SubscriberTransportAddress, out returnAddress)) + { + context.Message.Headers.TryGetValue(Headers.ReplyToAddress, out returnAddress); + } + + var intent = (MessageIntentEnum)Enum.Parse(typeof(MessageIntentEnum), context.Message.Headers[Headers.MessageIntent], true); + if (intent != intentToHandle) + { + return; + } + + action(new SubscriptionEventArgs + { + MessageType = subscriptionMessageType, + SubscriberReturnAddress = returnAddress + }, scenarioContext); + } + } + + static string GetSubscriptionMessageTypeFrom(IncomingMessage msg) + { + string headerValue; + return msg.Headers.TryGetValue(Headers.SubscriptionMessageType, out headerValue) ? headerValue : null; + } + + Action action; + TContext scenarioContext; + MessageIntentEnum intentToHandle; + + internal class Registration : RegisterStep + { + public Registration(string id, Func behaviorFactory) + : base(id, typeof(SubscriptionBehavior), "notify subscription events", behaviorFactory) + { + InsertBeforeIfExists("ProcessSubscriptionRequests"); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehaviorExtensions.cs b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehaviorExtensions.cs new file mode 100644 index 00000000000..e184ef5b4a0 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionBehaviorExtensions.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using AcceptanceTesting; + + static class SubscriptionBehaviorExtensions + { + public static void OnEndpointSubscribed(this EndpointConfiguration configuration, Action action) where TContext : ScenarioContext + { + configuration.Pipeline.Register(new SubscriptionBehavior.Registration("NotifySubscriptionBehavior", builder => + { + var context = builder.Build(); + return new SubscriptionBehavior(action, context, MessageIntentEnum.Subscribe); + })); + } + + public static void OnEndpointUnsubscribed(this EndpointConfiguration configuration, Action action) where TContext : ScenarioContext + { + configuration.Pipeline.Register(new SubscriptionBehavior.Registration("NotifyUnsubscriptionBehavior", builder => + { + var context = builder.Build(); + return new SubscriptionBehavior(action, context, MessageIntentEnum.Unsubscribe); + })); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/SubscriptionEventArgs.cs b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionEventArgs.cs new file mode 100644 index 00000000000..fb19ac57527 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/SubscriptionEventArgs.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + public class SubscriptionEventArgs + { + /// + /// The address of the subscriber. + /// + public string SubscriberReturnAddress { get; set; } + + /// + /// The type of message the client subscribed to. + /// + public string MessageType { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_base_event_from_2_publishers.cs b/src/NServiceBus.AcceptanceTests/Routing/When_base_event_from_2_publishers.cs new file mode 100644 index 00000000000..aba9185e086 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_base_event_from_2_publishers.cs @@ -0,0 +1,123 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + + public class When_base_event_from_2_publishers : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_events_from_all_publishers() + { + var context = await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.SubscribedToPublisher1, session => session.Publish(new DerivedEvent1())) + ) + .WithEndpoint(b => + b.When(c => c.SubscribedToPublisher2, session => session.Publish(new DerivedEvent2())) + ) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, async (session, c) => + { + await session.Subscribe(); + await session.Subscribe(); + + if (c.HasNativePubSubSupport) + { + c.SubscribedToPublisher1 = true; + c.SubscribedToPublisher2 = true; + } + })) + .Done(c => c.GotTheEventFromPublisher1 && c.GotTheEventFromPublisher2) + .Run(); + + Assert.True(context.GotTheEventFromPublisher1); + Assert.True(context.GotTheEventFromPublisher2); + } + + public class Context : ScenarioContext + { + public bool GotTheEventFromPublisher1 { get; set; } + public bool GotTheEventFromPublisher2 { get; set; } + public bool SubscribedToPublisher1 { get; set; } + public bool SubscribedToPublisher2 { get; set; } + } + + public class Publisher1 : EndpointConfigurationBuilder + { + public Publisher1() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + context.AddTrace("Publisher1 SubscriberReturnAddress=" + s.SubscriberReturnAddress); + if (s.SubscriberReturnAddress.Contains("Subscriber1")) + { + context.SubscribedToPublisher1 = true; + } + })); + } + } + + public class Publisher2 : EndpointConfigurationBuilder + { + public Publisher2() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + context.AddTrace("Publisher2 SubscriberReturnAddress=" + s.SubscriberReturnAddress); + + if (s.SubscriberReturnAddress.Contains("Subscriber1")) + { + context.SubscribedToPublisher2 = true; + } + })); + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher1)) + .AddMapping(typeof(Publisher2)); + } + + public class BaseEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(BaseEvent message, IMessageHandlerContext context) + { + if (message.GetType().FullName.Contains("DerivedEvent1")) + { + Context.GotTheEventFromPublisher1 = true; + } + if (message.GetType().FullName.Contains("DerivedEvent2")) + { + Context.GotTheEventFromPublisher2 = true; + } + + return Task.FromResult(0); + } + } + } + + [Serializable] + public class BaseEvent : IEvent + { + } + + [Serializable] + public class DerivedEvent1 : BaseEvent + { + } + + [Serializable] + public class DerivedEvent2 : BaseEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_configure_routes_for_unobtrusive_messages.cs b/src/NServiceBus.AcceptanceTests/Routing/When_configure_routes_for_unobtrusive_messages.cs new file mode 100644 index 00000000000..bedd46ed37c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_configure_routes_for_unobtrusive_messages.cs @@ -0,0 +1,100 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NUnit.Framework; + + public class When_configure_routes_for_unobtrusive_messages : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_routes_from_routing_api() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(s => s.Send(new SomeCommand()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Run(); + + Assert.That(context.ReceivedMessage, Is.True); + } + + [Test] + public async Task Should_use_routes_from_endpoint_mapping() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(s => s.Send(new SomeCommand()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Run(); + + Assert.That(context.ReceivedMessage, Is.True); + } + + public class Context : ScenarioContext + { + public bool ReceivedMessage { get; set; } + } + + public class SendingEndpointUsingRoutingApi : EndpointConfigurationBuilder + { + public SendingEndpointUsingRoutingApi() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t == typeof(SomeCommand)); + + var routing = new RoutingSettings(c.GetSettings()); + routing.RouteToEndpoint(typeof(SomeCommand).Assembly, Conventions.EndpointNamingConvention(typeof(ReceivingEndpoint))); + }).ExcludeType(); //exclude type to simulate an unobtrusive message assembly which isn't automatically loaded. + } + } + + public class SendingEndpointUsingEndpointMapping : EndpointConfigurationBuilder + { + public SendingEndpointUsingEndpointMapping() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t == typeof(SomeCommand)); + }) + .AddMapping(typeof(ReceivingEndpoint)) + .ExcludeType(); //exclude type to simulate an unobtrusive message assembly which isn't automatically loaded. + } + } + + public class ReceivingEndpoint : EndpointConfigurationBuilder + { + public ReceivingEndpoint() + { + EndpointSetup(c => c + .Conventions() + .DefiningCommandsAs(t => t == typeof(SomeCommand))); + } + + public class CommandHandler : IHandleMessages + { + Context testContext; + + public CommandHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(SomeCommand message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class SomeCommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_extending_command_routing.cs b/src/NServiceBus.AcceptanceTests/Routing/When_extending_command_routing.cs new file mode 100644 index 00000000000..6bdfcbc1273 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_extending_command_routing.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using NServiceBus.Routing; + using NUnit.Framework; + + public class When_extending_command_routing : NServiceBusAcceptanceTest + { + static string ReceiverEndpoint => Conventions.EndpointNamingConvention(typeof(Receiver)); + + [Test] + public async Task Should_route_commands_correctly() + { + var ctx = await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.EndpointsStarted, async session => + { + await session.Send(new MyCommand()); + await session.Send(new MyCommand()); + await session.Send(new MyCommand()); + await session.Send(new MyCommand()); + }) + ) + .WithEndpoint() + .Done(c => c.MessageDelivered >= 4) + .Run(); + + Assert.IsTrue(ctx.MessageDelivered >= 4); + } + + public class Context : ScenarioContext + { + public int MessageDelivered; + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.GetSettings().GetOrCreate() + .AddOrReplaceRoutes("CustomRoutingFeature", new List + { + new RouteTableEntry(typeof(MyCommand), UnicastRoute.CreateFromEndpointName(ReceiverEndpoint)) + }); + c.GetSettings().GetOrCreate() + .AddOrReplaceInstances("CustomRoutingFeature", new List + { + new EndpointInstance(ReceiverEndpoint, "XYZ"), + new EndpointInstance(ReceiverEndpoint, "ABC") + }); + c.GetSettings().GetOrCreate() + .SetDistributionStrategy(new XyzDistributionStrategy(ReceiverEndpoint)); + }); + } + + class XyzDistributionStrategy : DistributionStrategy + { + public XyzDistributionStrategy(string endpoint) : base(endpoint, DistributionStrategyScope.Send) + { + } + + public override string SelectReceiver(string[] receiverAddresses) + { + return receiverAddresses.First(x => x.Contains("XYZ")); + } + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => c.MakeInstanceUniquelyAddressable("XYZ")); + } + + public class MyCommandHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyCommand evnt, IMessageHandlerContext context) + { + Interlocked.Increment(ref Context.MessageDelivered); + return Task.FromResult(0); + } + } + } + + public class MyCommand : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_overriding_local_address.cs b/src/NServiceBus.AcceptanceTests/Routing/When_overriding_local_address.cs new file mode 100644 index 00000000000..c0f6961b0e0 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_overriding_local_address.cs @@ -0,0 +1,76 @@ +namespace ServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using NServiceBus; + using NServiceBus.AcceptanceTesting; + using NServiceBus.AcceptanceTests; + using NServiceBus.AcceptanceTests.EndpointTemplates; + using NUnit.Framework; + using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; + + public class When_overriding_local_address : NServiceBusAcceptanceTest + { + public static string ReceiverEndpointName => Conventions.EndpointNamingConvention(typeof(Receiver)); + public static string ReceiverQueueName => "q_" + ReceiverEndpointName; + + [Test] + public async Task Should_use_the_provided_address_as_input_queue_name() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(c => + { + var options = new SendOptions(); + options.SetDestination(ReceiverQueueName); + return c.Send(new Message(), options); + })) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Run(); + + Assert.That(context.ReceivedMessage, Is.True); + } + + public class Context : ScenarioContext + { + public bool ReceivedMessage { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + }); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => c.OverrideLocalAddress(ReceiverQueueName)); + } + + public class MessageHandler : IHandleMessages + { + Context testContext; + + public MessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing.cs new file mode 100644 index 00000000000..cf4aae654b9 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing.cs @@ -0,0 +1,206 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing : NServiceBusAcceptanceTest + { + [Test] + public async Task Issue_1851() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber3Subscribed, session => session.Publish()) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.Subscriber3Subscribed = true; + } + })) + .Done(c => c.Subscriber3GotTheEvent) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.Subscriber3GotTheEvent)) + .Run(); + } + + [Test] + public async Task Should_be_delivered_to_all_subscribers() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber1Subscribed && c.Subscriber2Subscribed, (session, c) => + { + c.AddTrace("Both subscribers is subscribed, going to publish MyEvent"); + + var options = new PublishOptions(); + + options.SetHeader("MyHeader", "SomeValue"); + return session.Publish(new MyEvent(), options); + }) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + if (context.HasNativePubSubSupport) + { + context.Subscriber1Subscribed = true; + context.AddTrace("Subscriber1 is now subscribed (at least we have asked the broker to be subscribed)"); + } + else + { + context.AddTrace("Subscriber1 has now asked to be subscribed to MyEvent"); + } + })) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.Subscriber2Subscribed = true; + context.AddTrace("Subscriber2 is now subscribed (at least we have asked the broker to be subscribed)"); + } + else + { + context.AddTrace("Subscriber2 has now asked to be subscribed to MyEvent"); + } + })) + .Done(c => c.Subscriber1GotTheEvent && c.Subscriber2GotTheEvent) + .Repeat(r => r.For(Transports.Default)) + .Should(c => + { + Assert.True(c.Subscriber1GotTheEvent); + Assert.True(c.Subscriber2GotTheEvent); + }) + .Run(TimeSpan.FromSeconds(10)); + } + + public class Context : ScenarioContext + { + public bool Subscriber1GotTheEvent { get; set; } + public bool Subscriber2GotTheEvent { get; set; } + public bool Subscriber3GotTheEvent { get; set; } + public bool Subscriber1Subscribed { get; set; } + public bool Subscriber2Subscribed { get; set; } + public bool Subscriber3Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber1")) + { + context.Subscriber1Subscribed = true; + context.AddTrace("Subscriber1 is now subscribed"); + } + + if (s.SubscriberReturnAddress.Contains("Subscriber2")) + { + context.AddTrace("Subscriber2 is now subscribed"); + context.Subscriber2Subscribed = true; + } + }); + b.DisableFeature(); + }); + } + } + + public class Publisher3 : EndpointConfigurationBuilder + { + public Publisher3() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber3")) + { + context.Subscriber3Subscribed = true; + } + })); + } + } + + public class Subscriber3 : EndpointConfigurationBuilder + { + public Subscriber3() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher3)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IFoo messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Subscriber3GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Assert.AreEqual(context.MessageHeaders["MyHeader"], "SomeValue"); + TestContext.Subscriber1GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class Subscriber2 : EndpointConfigurationBuilder + { + public Subscriber2() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Subscriber2GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public interface IFoo : IEvent + { + } + + [Serializable] + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_event_implementing_two_unrelated_interfaces.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_event_implementing_two_unrelated_interfaces.cs new file mode 100644 index 00000000000..b458d9b4a2b --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_event_implementing_two_unrelated_interfaces.cs @@ -0,0 +1,148 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_an_event_implementing_two_unrelated_interfaces : NServiceBusAcceptanceTest + { + [Test] + public async Task Event_should_be_published_using_instance_type() + { + await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => + b.When(c => c.EventASubscribed && c.EventBSubscribed, (session, ctx) => + { + var message = new CompositeEvent + { + ContextId = ctx.Id + }; + return session.Publish(message); + })) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.EventASubscribed = true; + context.EventBSubscribed = true; + } + })) + .Done(c => c.GotEventA && c.GotEventB) + .Repeat(r => r.For(Serializers.Xml)) + .Should(c => + { + Assert.True(c.GotEventA); + Assert.True(c.GotEventB); + }) + .Run(TimeSpan.FromSeconds(20)); + } + + public class Context : ScenarioContext + { + public Guid Id { get; set; } + public bool EventASubscribed { get; set; } + public bool EventBSubscribed { get; set; } + public bool GotEventA { get; set; } + public bool GotEventB { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber")) + { + if (s.MessageType == typeof(IEventA).AssemblyQualifiedName) + { + context.EventASubscribed = true; + } + if (s.MessageType == typeof(IEventB).AssemblyQualifiedName) + { + context.EventBSubscribed = true; + } + } + })); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.Conventions().DefiningMessagesAs(t => t != typeof(CompositeEvent) && typeof(IMessage).IsAssignableFrom(t) && + typeof(IMessage) != t && + typeof(IEvent) != t && + typeof(ICommand) != t); + + c.Conventions().DefiningEventsAs(t => t != typeof(CompositeEvent) && typeof(IEvent).IsAssignableFrom(t) && typeof(IEvent) != t); + c.DisableFeature(); + }) + .AddMapping(typeof(Publisher)) + .AddMapping(typeof(Publisher)); + } + + public class EventAHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IEventA @event, IMessageHandlerContext context) + { + if (@event.ContextId != Context.Id) + { + return Task.FromResult(0); + } + Context.GotEventA = true; + + return Task.FromResult(0); + } + } + + public class EventBHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IEventB @event, IMessageHandlerContext context) + { + if (@event.ContextId != Context.Id) + { + return Task.FromResult(0); + } + + Context.GotEventB = true; + + return Task.FromResult(0); + } + } + } + + public class CompositeEvent : IEventA, IEventB + { + public Guid ContextId { get; set; } + public string StringProperty { get; set; } + public int IntProperty { get; set; } + } + + public interface IEventA : IEvent + { + Guid ContextId { get; set; } + string StringProperty { get; set; } + } + + public interface IEventB : IEvent + { + Guid ContextId { get; set; } + int IntProperty { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface.cs new file mode 100644 index 00000000000..f4c02f68f23 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_an_interface : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_event_for_non_xml() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, (session, ctx) => session.Publish())) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + if (context.HasNativePubSubSupport) + { + context.Subscribed = true; + } + })) + .Done(c => c.GotTheEvent) + .Repeat(r => r.For(Serializers.Json)) + .Should(c => + { + Assert.True(c.GotTheEvent); + Assert.AreEqual(typeof(MyEvent), c.EventTypePassedToRouting); + }) + .Run(); + } + + class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool Subscribed { get; set; } + public Type EventTypePassedToRouting { get; set; } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.Pipeline.Register("EventTypeSpy", new EventTypeSpy((Context)ScenarioContext), "EventTypeSpy"); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber")) + { + context.Subscribed = true; + } + }); + }); + } + + class EventTypeSpy : IBehavior + { + public EventTypeSpy(Context testContext) + { + this.testContext = testContext; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + testContext.EventTypePassedToRouting = context.Message.MessageType; + return next(context); + } + + Context testContext; + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public interface MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface_with_unobtrusive.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface_with_unobtrusive.cs new file mode 100644 index 00000000000..0b8f552a14e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_an_interface_with_unobtrusive.cs @@ -0,0 +1,108 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Pipeline; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_an_interface_with_unobtrusive : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_event_for_non_xml() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, (session, ctx) => session.Publish())) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + if (context.HasNativePubSubSupport) + { + context.Subscribed = true; + } + })) + .Done(c => c.GotTheEvent) + .Repeat(r => r.For(Serializers.Json)) + .Should(c => + { + Assert.True(c.GotTheEvent); + Assert.AreEqual(typeof(MyEvent), c.EventTypePassedToRouting); + }) + .Run(); + } + + class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool Subscribed { get; set; } + public Type EventTypePassedToRouting { get; set; } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.Conventions().DefiningEventsAs(t => t.Namespace != null && t.Name.EndsWith("Event")); + c.Pipeline.Register("EventTypeSpy", typeof(EventTypeSpy), "EventTypeSpy"); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber")) + { + context.Subscribed = true; + } + }); + }).ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + + class EventTypeSpy : IBehavior + { + public EventTypeSpy(Context testContext) + { + this.testContext = testContext; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + testContext.EventTypePassedToRouting = context.Message.MessageType; + return next(context); + } + + Context testContext; + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.Conventions().DefiningEventsAs(t => t.Namespace != null && t.Name.EndsWith("Event")); + c.DisableFeature(); + }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public interface MyEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_using_root_type.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_using_root_type.cs new file mode 100644 index 00000000000..2c968131361 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_using_root_type.cs @@ -0,0 +1,94 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_using_root_type : NServiceBusAcceptanceTest + { + [Test] + public async Task Event_should_be_published_using_instance_type() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber1Subscribed, session => + { + IMyEvent message = new EventMessage(); + + return session.Publish(message); + })) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.Subscriber1Subscribed = true; + } + })) + .Done(c => c.Subscriber1GotTheEvent) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.Subscriber1GotTheEvent)) + .Run(TimeSpan.FromSeconds(20)); + } + + public class Context : ScenarioContext + { + public bool Subscriber1GotTheEvent { get; set; } + public bool Subscriber1Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber1")) + { + context.Subscriber1Subscribed = true; + } + })); + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(EventMessage messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Subscriber1GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class EventMessage : IMyEvent + { + public Guid EventId { get; set; } + public DateTime? Time { get; set; } + public TimeSpan Duration { get; set; } + } + + public interface IMyEvent : IEvent + { + Guid EventId { get; set; } + DateTime? Time { get; set; } + TimeSpan Duration { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_only_local_messagehandlers.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_only_local_messagehandlers.cs new file mode 100644 index 00000000000..6274576e9ab --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_only_local_messagehandlers.cs @@ -0,0 +1,108 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_with_only_local_messagehandlers : NServiceBusAcceptanceTest + { + [Test] + public Task Should_trigger_the_catch_all_handler_for_message_driven_subscriptions() + { + return Scenario.Define() + .WithEndpoint(b => + b.When(c => c.LocalEndpointSubscribed, session => session.Publish(new EventHandledByLocalEndpoint()))) + .Done(c => c.CatchAllHandlerGotTheMessage) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.CatchAllHandlerGotTheMessage)) + .Run(); + } + + [Test] + public Task Should_trigger_the_catch_all_handler_for_publishers_with_centralized_pubsub() + { + return Scenario.Define() + .WithEndpoint(b => + { + b.When(session => session.Subscribe()); + b.When(c => c.EndpointsStarted, (session, context) => session.Publish(new EventHandledByLocalEndpoint())); + }) + .Done(c => c.CatchAllHandlerGotTheMessage) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.CatchAllHandlerGotTheMessage)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool CatchAllHandlerGotTheMessage { get; set; } + + public bool LocalEndpointSubscribed { get; set; } + } + + public class MessageDrivenPublisher : EndpointConfigurationBuilder + { + public MessageDrivenPublisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => { context.LocalEndpointSubscribed = true; })) + .AddMapping(typeof(MessageDrivenPublisher)); //an explicit mapping is needed + } + + class CatchAllHandler : IHandleMessages //not enough for auto subscribe to work + { + public Context Context { get; set; } + + public Task Handle(IEvent message, IMessageHandlerContext context) + { + Context.CatchAllHandlerGotTheMessage = true; + + return Task.FromResult(0); + } + } + + class DummyHandler : IHandleMessages //explicit handler for the event is needed + { + public Task Handle(EventHandledByLocalEndpoint message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + public class CentralizedStoragePublisher : EndpointConfigurationBuilder + { + public CentralizedStoragePublisher() + { + EndpointSetup() + .AddMapping(typeof(CentralizedStoragePublisher)); //an explicit mapping may be needed, depends on the technology underneath; + } + + class CatchAllHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(IEvent message, IMessageHandlerContext context) + { + Context.CatchAllHandlerGotTheMessage = true; + return Task.FromResult(0); + } + } + + class DummyHandler : IHandleMessages //explicit handler for the event is needed + { + public Task Handle(EventHandledByLocalEndpoint message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + [Serializable] + public class EventHandledByLocalEndpoint : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_overridden_local_address.cs b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_overridden_local_address.cs new file mode 100644 index 00000000000..10d933b700c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_publishing_with_overridden_local_address.cs @@ -0,0 +1,84 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_publishing_with_overridden_local_address : NServiceBusAcceptanceTest + { + [Test, Explicit("This test fails against RabbitMQ")] + public async Task Should_be_delivered_to_all_subscribers() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber1Subscribed, session => session.Publish(new MyEvent())) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + + if (context.HasNativePubSubSupport) + { + context.Subscriber1Subscribed = true; + } + })) + .Done(c => c.Subscriber1GotTheEvent) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.Subscriber1GotTheEvent)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool Subscriber1GotTheEvent { get; set; } + public bool Subscriber1Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("myinputqueue")) + { + context.Subscriber1Subscribed = true; + } + })); + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(builder => + { + builder.DisableFeature(); + //builder.OverrideLocalAddress("myInputQueue"); Fix in 133 + }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Subscriber1GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_code.cs b/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_code.cs new file mode 100644 index 00000000000..d4027256573 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_code.cs @@ -0,0 +1,80 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using AcceptanceTesting.Customization; + using ScenarioDescriptors; + + public class When_registering_publishers_unobtrusive_messages_code : NServiceBusAcceptanceTest + { + [Test] + public Task Should_deliver_event() + { + return Scenario.Define() + .WithEndpoint(e => e + .When(c => c.Subscribed, s => s.Publish(new SomeEvent()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Repeat(r => r.For()) + .Should(context => + { + Assert.That(context.Subscribed, Is.True); + Assert.That(context.ReceivedMessage, Is.True); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool Subscribed { get; set; } + public bool ReceivedMessage { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.OnEndpointSubscribed((e, ctx) => ctx.Subscribed = true); + c.Conventions().DefiningEventsAs(t => t == typeof(SomeEvent)); + }).ExcludeType(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.Conventions().DefiningEventsAs(t => t == typeof(SomeEvent)); + + c.MessageDrivenPubSubRouting().RegisterPublisher(typeof(SomeEvent).Assembly, Conventions.EndpointNamingConvention(typeof(Publisher))); + }); + } + + public class EventHandler : IHandleMessages + { + Context testContext; + + public EventHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(SomeEvent message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class SomeEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_config.cs b/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_config.cs new file mode 100644 index 00000000000..0ef0e10773a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_registering_publishers_unobtrusive_messages_config.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_registering_publishers_unobtrusive_messages_config : NServiceBusAcceptanceTest + { + [Test] + public Task Should_deliver_event() + { + return Scenario.Define() + .WithEndpoint(e => e + .When(c => c.Subscribed, s => s.Publish(new SomeEvent()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Repeat(r => r.For()) + .Should(context => + { + Assert.That(context.Subscribed, Is.True); + Assert.That(context.ReceivedMessage, Is.True); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool Subscribed { get; set; } + public bool ReceivedMessage { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.OnEndpointSubscribed((e, ctx) => ctx.Subscribed = true); + c.Conventions().DefiningEventsAs(t => t == typeof(SomeEvent)); + }).ExcludeType(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c + .Conventions().DefiningEventsAs(t => t == typeof(SomeEvent))) + .AddMapping(typeof(Publisher)); + } + + public class EventHandler : IHandleMessages + { + Context testContext; + + public EventHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(SomeEvent message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class SomeEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message.cs b/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message.cs new file mode 100644 index 00000000000..88ac15f24ea --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message.cs @@ -0,0 +1,110 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using Conventions = AcceptanceTesting.Customization.Conventions; + + public class When_replying_to_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_reply_to_originator() + { + var ctx = await Scenario.Define() + .WithEndpoint(c => c + .When(b => b.Send(new MyMessage()))) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.SendingEndpointGotResponse) + .Run(); + + Assert.IsTrue(ctx.SendingEndpointGotResponse); + Assert.IsFalse(ctx.OtherEndpointGotResponse); + } + + [Test] + public async Task Should_reply_to_configured_return_address() + { + var ctx = await Scenario.Define() + .WithEndpoint(c => c + .CustomConfig(b => b.OverridePublicReturnAddress(Conventions.EndpointNamingConvention(typeof(OtherEndpoint)))) + .When(b => b.Send(new MyMessage()))) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.OtherEndpointGotResponse) + .Run(); + + Assert.IsTrue(ctx.OtherEndpointGotResponse); + Assert.IsFalse(ctx.SendingEndpointGotResponse); + } + + public class Context : ScenarioContext + { + public bool SendingEndpointGotResponse { get; set; } + public bool OtherEndpointGotResponse { get; set; } + } + + public class SendingEndpoint : EndpointConfigurationBuilder + { + public SendingEndpoint() + { + EndpointSetup().AddMapping(typeof(ReplyingEndpoint)); + } + + public class ResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyReply messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.SendingEndpointGotResponse = true; + return Task.FromResult(0); + } + } + } + + public class OtherEndpoint : EndpointConfigurationBuilder + { + public OtherEndpoint() + { + EndpointSetup(); + } + + public class ResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyReply messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.OtherEndpointGotResponse = true; + return Task.FromResult(0); + } + } + } + + public class ReplyingEndpoint : EndpointConfigurationBuilder + { + public ReplyingEndpoint() + { + EndpointSetup(); + } + + public class MessageHandler : IHandleMessages + { + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + return context.Reply(new MyReply()); + } + } + } + + public class MyMessage : IMessage + { + } + + public class MyReply : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message_with_interface_and_unobtrusive.cs b/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message_with_interface_and_unobtrusive.cs new file mode 100644 index 00000000000..ee2c0389850 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_replying_to_message_with_interface_and_unobtrusive.cs @@ -0,0 +1,95 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_replying_to_message_with_interface_and_unobtrusive : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_reply_to_originator() + { + var ctx = await Scenario.Define() + .WithEndpoint(c => c + .When(b => b.Send(new MyMessage()))) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.SendingEndpointGotResponse) + .Run(); + + Assert.IsTrue(ctx.SendingEndpointGotResponse); + Assert.IsFalse(ctx.OtherEndpointGotResponse); + } + + public class Context : ScenarioContext + { + public bool SendingEndpointGotResponse { get; set; } + public bool OtherEndpointGotResponse { get; set; } + } + + public class SendingEndpoint : EndpointConfigurationBuilder + { + public SendingEndpoint() + { + EndpointSetup(c => c.Conventions().DefiningMessagesAs(t => t.Namespace != null && t.Name.StartsWith("My"))) + .AddMapping(typeof(ReplyingEndpoint)); + } + + public class ResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyReply messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.SendingEndpointGotResponse = true; + return Task.FromResult(0); + } + } + } + + public class OtherEndpoint : EndpointConfigurationBuilder + { + public OtherEndpoint() + { + EndpointSetup(c => c.Conventions().DefiningMessagesAs(t => t.Namespace != null && t.Name.StartsWith("My"))); + } + + public class ResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyReply messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.OtherEndpointGotResponse = true; + return Task.FromResult(0); + } + } + } + + public class ReplyingEndpoint : EndpointConfigurationBuilder + { + public ReplyingEndpoint() + { + EndpointSetup(c => c.Conventions().DefiningMessagesAs(t => t.Namespace != null && t.Name.StartsWith("My"))) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + + public class MessageHandler : IHandleMessages + { + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + return context.Reply(m => { }); + } + } + } + + public class MyMessage + { + } + + public interface MyReply + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_sending_a_base_command.cs b/src/NServiceBus.AcceptanceTests/Routing/When_sending_a_base_command.cs new file mode 100644 index 00000000000..cf927b717be --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_sending_a_base_command.cs @@ -0,0 +1,89 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_a_base_command : NServiceBusAcceptanceTest + { + [Test] + public async Task Route_for_derived_command_should_be_used() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.EndpointsStarted, async session => + { + await session.Send(new BaseCommand()); + })) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.UsedBaseRoute || c.UsedDerivedRoute) + .Run(); + + Assert.True(context.UsedDerivedRoute); + Assert.False(context.UsedBaseRoute); + } + + public class Context : ScenarioContext + { + public bool UsedDerivedRoute { get; set; } + public bool UsedBaseRoute { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup() + .AddMapping(typeof(QueueSpy)) + .AddMapping(typeof(Receiver)); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(BaseCommand messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.UsedDerivedRoute = true; + return Task.FromResult(0); + } + } + } + + public class QueueSpy : EndpointConfigurationBuilder + { + public QueueSpy() + { + EndpointSetup(); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(BaseCommand messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.UsedBaseRoute = true; + return Task.FromResult(0); + } + } + } + + public class BaseCommand : ICommand + { + } + + public class DerivedCommand : BaseCommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_assembly.cs b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_assembly.cs new file mode 100644 index 00000000000..b5e0d9ed369 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_assembly.cs @@ -0,0 +1,55 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_non_message_with_routing_configured_by_assembly : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw_when_sending() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.Send(new NonMessage()); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + StringAssert.Contains("No destination specified for message", context.Exception.ToString()); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.RouteToEndpoint(typeof(NonMessage).Assembly, "Destination"); + }); + } + } + + public class NonMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_type.cs b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_type.cs new file mode 100644 index 00000000000..5de361904c0 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_by_type.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + //All other variants of configuration-time routing checks are covered by unit tests. + public class When_sending_non_message_with_routing_configured_by_type : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_when_configuring_routing() + { + var exception = Assert.ThrowsAsync(async () => await Scenario.Define() + .WithEndpoint(c => c + .When(b => b.Send(new NonMessage()))) + .Done(c => c.EndpointsStarted) + .Run()); + + StringAssert.Contains("Cannot configure routing for type", exception.ToString()); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.RouteToEndpoint(typeof(NonMessage), "Destination"); + }); + } + } + + public class NonMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_via_mappings.cs b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_via_mappings.cs new file mode 100644 index 00000000000..ceb60a8dbe5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_sending_non_message_with_routing_configured_via_mappings.cs @@ -0,0 +1,61 @@ +namespace NServiceBus.AcceptanceTests.BestPractices +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_sending_non_message_with_routing_configured_via_mappings : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw_when_sending() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + await session.Send(new NonMessage()); + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + StringAssert.Contains("No destination specified for messag", context.Exception.ToString()); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup() + .AddMapping(typeof(Receiver)); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(); + } + } + + public class NonMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/When_using_instance_ids.cs b/src/NServiceBus.AcceptanceTests/Routing/When_using_instance_ids.cs new file mode 100644 index 00000000000..d0cbbf06c07 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/When_using_instance_ids.cs @@ -0,0 +1,81 @@ +namespace NServiceBus.AcceptanceTests.ScaleOut +{ + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NServiceBus.Routing; + using NUnit.Framework; + using Routing; + + public class When_using_instance_ids : NServiceBusAcceptanceTest + { + static string ReceiverEndpoint => Conventions.EndpointNamingConvention(typeof(Receiver)); + + [Test] + public async Task Should_be_addressable_both_by_shared_queue_and_specific_queue() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When(s => s.Send(new MyMessage()))) + .WithEndpoint(b => b.When(s => s.Send(new MyMessage()))) + .Done(c => c.MessagesReceived > 1) + .Run(); + + Assert.AreEqual(2, context.MessagesReceived); + } + + public class Context : ScenarioContext + { + public int MessagesReceived; + } + + public class UnawareSender : EndpointConfigurationBuilder + { + public UnawareSender() + { + EndpointSetup((c, r) => + { + c.UseTransport(r.GetTransportType()).Routing().RouteToEndpoint(typeof(MyMessage), ReceiverEndpoint); + }); + } + } + + public class AwareSender : EndpointConfigurationBuilder + { + public AwareSender() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.RouteToEndpoint(typeof(MyMessage), ReceiverEndpoint); + routing.RegisterEndpointInstances(new EndpointInstance(ReceiverEndpoint, "XYZ")); + }); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => { c.MakeInstanceUniquelyAddressable("XYZ"); }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Interlocked.Increment(ref Context.MessagesReceived); + return Task.FromResult(0); + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Routing/when_replying_to_a_message_sent_to_specific_instance.cs b/src/NServiceBus.AcceptanceTests/Routing/when_replying_to_a_message_sent_to_specific_instance.cs new file mode 100644 index 00000000000..5ad882e1ba2 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Routing/when_replying_to_a_message_sent_to_specific_instance.cs @@ -0,0 +1,80 @@ +namespace NServiceBus.AcceptanceTests.ScaleOut +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using EndpointTemplates; + using NServiceBus.Routing; + using NUnit.Framework; + using Routing; + + public class When_replying_to_a_message_sent_to_specific_instance : NServiceBusAcceptanceTest + { + static string ReceiverEndpoint => Conventions.EndpointNamingConvention(typeof(Receiver)); + + [Test] + public async Task Reply_address_should_be_set_to_shared_endpoint_queue() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When(s => s.Send(new MyRequest()))) + .Done(c => c.ReplyToAddress != null) + .Run(); + + StringAssert.DoesNotContain("XZY", context.ReplyToAddress); + } + + public class Context : ScenarioContext + { + public string ReplyToAddress { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup((c, r) => + { + var routing = c.UseTransport(r.GetTransportType()).Routing(); + routing.RouteToEndpoint(typeof(MyRequest), ReceiverEndpoint); + routing.RegisterEndpointInstances(new EndpointInstance(ReceiverEndpoint, "XYZ")); + }); + } + + public class MyResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyResponse message, IMessageHandlerContext context) + { + Context.ReplyToAddress = context.MessageHeaders[Headers.ReplyToAddress]; + return Task.FromResult(0); + } + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => { c.MakeInstanceUniquelyAddressable("XYZ"); }); + } + + public class MyRequestHandler : IHandleMessages + { + public Task Handle(MyRequest message, IMessageHandlerContext context) + { + return context.Reply(new MyResponse()); + } + } + } + + public class MyRequest : IMessage + { + } + + public class MyResponse : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/RunDescriptorExtensions.cs b/src/NServiceBus.AcceptanceTests/RunDescriptorExtensions.cs new file mode 100644 index 00000000000..e9b13de9f15 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/RunDescriptorExtensions.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using AcceptanceTesting.Support; + using ScenarioDescriptors; + + public static class RunDescriptorExtensions + { + public static Type GetTransportType(this RunDescriptor runDescriptor) + { + Type transportType; + if (!runDescriptor.Settings.TryGet("Transport", out transportType)) + { + return Transports.Default.Settings.Get("Transport"); + } + + return transportType; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/Issue_1819.cs b/src/NServiceBus.AcceptanceTests/Sagas/Issue_1819.cs deleted file mode 100644 index 00aca53c71a..00000000000 --- a/src/NServiceBus.AcceptanceTests/Sagas/Issue_1819.cs +++ /dev/null @@ -1,122 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Sagas -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - using Saga; - - public class Issue_1819 : NServiceBusAcceptanceTest - { - [Test] - public void Run() - { - var context = new Context { Id = Guid.NewGuid() }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new StartSaga1 { ContextId = c.Id }))) - .Done(c => (c.Saga1TimeoutFired && c.Saga2TimeoutFired) || c.SagaNotFound) - .Run(TimeSpan.FromSeconds(20)); - - Assert.IsFalse(context.SagaNotFound); - Assert.IsTrue(context.Saga1TimeoutFired); - Assert.IsTrue(context.Saga2TimeoutFired); - } - - public class Context : ScenarioContext - { - public Guid Id { get; set; } - public bool Saga1TimeoutFired { get; set; } - public bool Saga2TimeoutFired { get; set; } - public bool SagaNotFound { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(); - } - - public class Saga1 : Saga, IAmStartedByMessages, IHandleTimeouts, IHandleTimeouts - { - public Context Context { get; set; } - - public void Handle(StartSaga1 message) - { - if (message.ContextId != Context.Id) return; - - RequestTimeout(TimeSpan.FromSeconds(5), new Saga1Timeout { ContextId = Context.Id }); - RequestTimeout(new DateTime(2011, 10, 14, 23, 08, 0, DateTimeKind.Local), new Saga2Timeout { ContextId = Context.Id }); - } - - public void Timeout(Saga1Timeout state) - { - MarkAsComplete(); - - if (state.ContextId != Context.Id) return; - Context.Saga1TimeoutFired = true; - } - - public void Timeout(Saga2Timeout state) - { - if (state.ContextId != Context.Id) return; - Context.Saga2TimeoutFired = true; - } - - public class Saga1Data : ContainSagaData - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - - public class SagaNotFound : IHandleSagaNotFound - { - public Context Context { get; set; } - - public void Handle(object message) - { - if (((dynamic)message).ContextId != Context.Id) return; - - Context.SagaNotFound = true; - } - } - - public class CatchAllMessageHandler : IHandleMessages - { - public void Handle(object message) - { - - } - } - - public class Foo : ISpecifyMessageHandlerOrdering - { - public void SpecifyOrder(Order order) - { - order.SpecifyFirst(); - } - } - } - - [Serializable] - public class StartSaga1 : ICommand - { - public Guid ContextId { get; set; } - } - - - public class Saga1Timeout - { - public Guid ContextId { get; set; } - } - - public class Saga2Timeout - { - public Guid ContextId { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/Issue_2044.cs b/src/NServiceBus.AcceptanceTests/Sagas/Issue_2044.cs deleted file mode 100644 index f3e266e90fc..00000000000 --- a/src/NServiceBus.AcceptanceTests/Sagas/Issue_2044.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Sagas -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - using Saga; - - public class Issue_2044 : NServiceBusAcceptanceTest - { - [Test] - public void Run() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Send(new MessageToSaga()))) - .WithEndpoint() - .Done(c => c.ReplyReceived) - .Run(); - - Assert.IsTrue(context.ReplyReceived); - } - - public class Context : ScenarioContext - { - public bool ReplyReceived { get; set; } - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup() - .AddMapping(typeof(ReceiverWithSaga)); - } - - public class ReplyHandler : IHandleMessages - { - public Context Context { get; set; } - - - public void Handle(Reply message) - { - Context.ReplyReceived = true; - } - } - } - - public class ReceiverWithSaga : EndpointConfigurationBuilder - { - public ReceiverWithSaga() - { - EndpointSetup(); - } - - public class Saga1 : Saga, IAmStartedByMessages, IHandleMessages - { - - public void Handle(StartSaga1 message) - { - } - - public void Handle(MessageToSaga message) - { - } - - public class Saga1Data : ContainSagaData - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - - public class SagaNotFound : IHandleSagaNotFound - { - public IBus Bus { get; set; } - - public void Handle(object message) - { - Bus.Reply(new Reply()); - } - } - } - - [Serializable] - public class StartSaga1 : ICommand - { - } - - [Serializable] - public class MessageToSaga : ICommand - { - } - - [Serializable] - public class Reply : IMessage - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_mapped_is_handled_by_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_mapped_is_handled_by_a_saga.cs new file mode 100644 index 00000000000..cdaa2ecbfc3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_mapped_is_handled_by_a_saga.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + [TestFixture] + public class When_a_base_class_mapped_is_handled_by_a_saga : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_find_existing_instance() + { + var correlationId = Guid.NewGuid(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => + { + var startSagaMessage = new StartSagaMessage + { + SomeId = correlationId + }; + return session.SendLocal(startSagaMessage); + })) + .Done(c => c.SecondMessageFoundExistingSaga) + .Run(TimeSpan.FromSeconds(20)); + + Assert.True(context.SecondMessageFoundExistingSaga); + } + + public class Context : ScenarioContext + { + public bool SecondMessageFoundExistingSaga { get; set; } + } + + public class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() + { + EndpointSetup(); + } + + public class BaseClassIsMappedSaga : Saga, + IAmStartedByMessages, + IAmStartedByMessages + { + public Context TestContext { get; set; } + + public Task Handle(SecondSagaMessage message, IMessageHandlerContext context) + { + TestContext.SecondMessageFoundExistingSaga = true; + return Task.FromResult(0); + } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + var sagaMessage = new SecondSagaMessage + { + SomeId = message.SomeId + }; + return context.SendLocal(sagaMessage); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + + public class BaseClassIsMappedSagaData : ContainSagaData + { + public virtual Guid SomeId { get; set; } + } + } + } + + public class StartSagaMessage : SagaMessageBase + { + } + + public class SecondSagaMessage : SagaMessageBase + { + } + + public class SagaMessageBase : IMessage + { + public Guid SomeId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_hits_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_hits_a_saga.cs deleted file mode 100644 index 8e59d9d0148..00000000000 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_hits_a_saga.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Sagas -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Saga; - using NUnit.Framework; - - [TestFixture] - public class When_a_base_class_message_hits_a_saga - { - [Test] - public void Should_find_existing_instance() - { - var correlationId = Guid.NewGuid(); - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => - { - bus.SendLocal(new StartSagaMessage - { - SomeId = correlationId - }); - bus.SendLocal(new StartSagaMessage - { - SomeId = correlationId - }); - })) - .Done(c => c.SecondMessageFoundExistingSaga) - .Run(TimeSpan.FromSeconds(20)); - - Assert.True(context.SecondMessageFoundExistingSaga); - } - - public class Context : ScenarioContext - { - public bool SecondMessageFoundExistingSaga { get; set; } - } - - public class SagaEndpoint : EndpointConfigurationBuilder - { - public SagaEndpoint() - { - EndpointSetup(); - } - - - - public class TestSaga : Saga, IAmStartedByMessages - { - public Context Context { get; set; } - - public void Handle(StartSagaMessageBase message) - { - if (Data.SomeId != Guid.Empty) - { - Context.SecondMessageFoundExistingSaga = true; - } - else - { - Data.SomeId = message.SomeId; - } - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s => s.SomeId); - } - - public class SagaData : ContainSagaData - { - [Unique] - public Guid SomeId { get; set; } - } - } - - } - - - public class StartSagaMessage : StartSagaMessageBase - { - } - public class StartSagaMessageBase : IMessage - { - public Guid SomeId { get; set; } - } - - - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_starts_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_starts_a_saga.cs new file mode 100644 index 00000000000..e5a4e518a37 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_base_class_message_starts_a_saga.cs @@ -0,0 +1,88 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + [TestFixture] + public class When_a_base_class_message_starts_a_saga : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_find_existing_instance() + { + var correlationId = Guid.NewGuid(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => + { + var startSagaMessage = new StartSagaMessage + { + SomeId = correlationId + }; + return session.SendLocal(startSagaMessage); + })) + .Done(c => c.SecondMessageFoundExistingSaga) + .Run(TimeSpan.FromSeconds(20)); + + Assert.True(context.SecondMessageFoundExistingSaga); + } + + public class Context : ScenarioContext + { + public bool SecondMessageFoundExistingSaga { get; set; } + } + + public class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() + { + EndpointSetup(); + } + + public class BaseClassStartsSaga : Saga, + IAmStartedByMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSagaMessageBase message, IMessageHandlerContext context) + { + if (Data.SomeId != Guid.Empty) + { + TestContext.SecondMessageFoundExistingSaga = true; + } + else + { + var startSagaMessage = new StartSagaMessage + { + SomeId = message.SomeId + }; + return context.SendLocal(startSagaMessage); + } + + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + + public class BaseClassStartsSagaData : ContainSagaData + { + public virtual Guid SomeId { get; set; } + } + } + } + + public class StartSagaMessage : StartSagaMessageBase + { + } + + public class StartSagaMessageBase : IMessage + { + public Guid SomeId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_existing_saga_instance_exists.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_existing_saga_instance_exists.cs index b676c78f0a3..89428900f81 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_a_existing_saga_instance_exists.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_existing_saga_instance_exists.cs @@ -1,83 +1,81 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; - using Saga; - using ScenarioDescriptors; public class When_a_existing_saga_instance_exists : NServiceBusAcceptanceTest { - static Guid IdThatSagaIsCorrelatedOn = Guid.NewGuid(); - [Test] - public void Should_hydrate_and_invoke_the_existing_instance() + public async Task Should_hydrate_and_invoke_the_existing_instance() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => - { - bus.SendLocal(new StartSagaMessage { SomeId = IdThatSagaIsCorrelatedOn }); - bus.SendLocal(new StartSagaMessage { SomeId = IdThatSagaIsCorrelatedOn, SecondMessage = true }); - })) - .Done(c => c.SecondMessageReceived) - .Repeat(r => r.For(Persistence.Default)) - .Should(c => Assert.AreEqual(c.FirstSagaInstance, c.SecondSagaInstance, "The same saga instance should be invoked invoked for both messages")) + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid() + }))) + .Done(c => c.SecondMessageReceived) + .Run(); - .Run(); + Assert.AreEqual(context.FirstSagaId, context.SecondSagaId, "The same saga instance should be invoked invoked for both messages"); } public class Context : ScenarioContext { public bool SecondMessageReceived { get; set; } - public Guid FirstSagaInstance { get; set; } - public Guid SecondSagaInstance { get; set; } + public Guid FirstSagaId { get; set; } + public Guid SecondSagaId { get; set; } } - public class SagaEndpoint : EndpointConfigurationBuilder + public class ExistingSagaInstanceEndpt : EndpointConfigurationBuilder { - public SagaEndpoint() + public ExistingSagaInstanceEndpt() { - EndpointSetup( - - builder => builder.Transactions().DoNotWrapHandlersExecutionInATransactionScope()); + EndpointSetup(); } - public class TestSaga : Saga, IAmStartedByMessages + public class TestSaga05 : Saga, IAmStartedByMessages { - public Context Context { get; set; } - public void Handle(StartSagaMessage message) + public Context TestContext { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { Data.SomeId = message.SomeId; if (message.SecondMessage) { - Context.SecondSagaInstance = Data.Id; - Context.SecondMessageReceived = true; + TestContext.SecondSagaId = Data.Id; + TestContext.SecondMessageReceived = true; } else { - Context.FirstSagaInstance = Data.Id; + TestContext.FirstSagaId = Data.Id; + return context.SendLocal(new StartSagaMessage + { + SomeId = message.SomeId, + SecondMessage = true + }); } + + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s=>s.SomeId); + .ToSaga(s => s.SomeId); } - } - public class TestSagaData : IContainSagaData + public class TestSagaData05 : IContainSagaData { + public virtual Guid SomeId { get; set; } public virtual Guid Id { get; set; } public virtual string Originator { get; set; } public virtual string OriginalMessageId { get; set; } - - [Unique] - public virtual Guid SomeId { get; set; } } } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists.cs index 564ffe1ad0a..fed634a2539 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists.cs @@ -1,20 +1,25 @@ namespace NServiceBus.AcceptanceTests.Sagas { - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Saga; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NServiceBus.Sagas; using NUnit.Framework; + using Persistence; [TestFixture] - public class When_a_finder_exists + public class When_a_finder_exists : NServiceBusAcceptanceTest { [Test] - public void Should_use_it_to_find_saga() + public async Task Should_use_it_to_find_saga() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSagaMessage()))) - .Done(c => c.FinderUsed) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage()))) + .Done(c => c.FinderUsed) + .Run(); Assert.True(context.FinderUsed); } @@ -28,36 +33,38 @@ public class SagaEndpoint : EndpointConfigurationBuilder { public SagaEndpoint() { - EndpointSetup(); + EndpointSetup(c => c.EnableFeature()); } - class CustomFinder : IFindSagas.Using + class CustomFinder : IFindSagas.Using { public Context Context { get; set; } - public TestSaga.SagaData FindBy(StartSagaMessage message) + + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) { Context.FinderUsed = true; - return null; + return Task.FromResult(default(TestSaga06.SagaData06)); } } - public class TestSaga : Saga, IAmStartedByMessages + public class TestSaga06 : Saga, IAmStartedByMessages { public Context Context { get; set; } - public void Handle(StartSagaMessage message) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + // not required because of CustomFinder } - public class SagaData : ContainSagaData + public class SagaData06 : ContainSagaData { } } - } public class StartSagaMessage : IMessage diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_context_information_added.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_context_information_added.cs new file mode 100644 index 00000000000..6a3039ebc6a --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_context_information_added.cs @@ -0,0 +1,107 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using Features; + using NServiceBus; + using NServiceBus.Pipeline; + using NServiceBus.Sagas; + using NUnit.Framework; + using Persistence; + + [TestFixture] + public class When_a_finder_exists_and_context_information_added : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_make_context_information_available() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage()))) + .Done(c => c.FinderUsed) + .Run(); + + Assert.True(context.FinderUsed); + Assert.AreEqual("SomeData", context.ContextBag.Get().SomeData); + } + + class Context : ScenarioContext + { + public bool FinderUsed { get; set; } + public ReadOnlyContextBag ContextBag { get; set; } + } + + class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() + { + EndpointSetup(c => + { + c.EnableFeature(); + c.Pipeline.Register(); + }); + } + + class CustomFinder : IFindSagas.Using + { + public Context Context { get; set; } + + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + Context.ContextBag = context; + Context.FinderUsed = true; + return Task.FromResult(default(TestSaga07.SagaData07)); + } + } + + public class TestSaga07 : Saga, IAmStartedByMessages + { + public Context Context { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + + public class SagaData07 : ContainSagaData + { + } + } + + public class BehaviorWhichAddsThingsToTheContext : IBehavior + { + public Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + context.Extensions.Set(new State + { + SomeData = "SomeData" + }); + + return next(context); + } + + public class State + { + public string SomeData { get; set; } + } + + public class Registration : RegisterStep + { + public Registration() : base("BehaviorWhichAddsThingsToTheContext", typeof(BehaviorWhichAddsThingsToTheContext), "BehaviorWhichAddsThingsToTheContext") + { + } + } + } + } + + public class StartSagaMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_found_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_found_saga.cs new file mode 100644 index 00000000000..b37b7d9ee5c --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_a_finder_exists_and_found_saga.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Extensibility; + using NServiceBus; + using NServiceBus.Sagas; + using NUnit.Framework; + using Persistence; + + [TestFixture] + public class When_a_finder_exists_and_found_saga : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_find_saga_and_not_correlate() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage { Property = "Test" }))) + .Done(c => c.Completed) + .Run(); + + Assert.True(context.FinderUsed); + } + + public class Context : ScenarioContext + { + public bool FinderUsed { get; set; } + public bool Completed { get; set; } + } + + public class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() + { + EndpointSetup(); + } + + public class CustomFinder : IFindSagas.Using + { + // ReSharper disable once MemberCanBePrivate.Global + public Context Context { get; set; } + + public ISagaPersister SagaPersister { get; set; } + + public async Task FindBy(SomeOtherMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + Context.FinderUsed = true; + var sagaInstance = new TestSaga08.SagaData08 + { + Property = "jfbsjdfbsdjh" + }; + //Make sure saga exists in the store. Persisters expect it there when they save saga instance after processing a message. + await SagaPersister.Save(sagaInstance, SagaCorrelationProperty.None, storageSession, new ContextBag()).ConfigureAwait(false); + return sagaInstance; + } + } + + public class TestSaga08 : Saga, + IAmStartedByMessages, + IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return context.SendLocal(new SomeOtherMessage()); + } + + public Task Handle(SomeOtherMessage message, IMessageHandlerContext context) + { + TestContext.Completed = true; + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(saga => saga.Property).ToSaga(saga => saga.Property); + // Mapping not required for SomeOtherMessage because CustomFinder used + } + + public class SagaData08 : ContainSagaData + { + public virtual string Property { get; set; } + } + } + } + + public class StartSagaMessage : IMessage + { + public string Property { get; set; } + } + + public class SomeOtherMessage : IMessage + { + public string Property { get; set; } + } + } +} diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_a_saga_message_goes_through_the_slr.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_a_saga_message_goes_through_the_slr.cs deleted file mode 100644 index 861743ba54e..00000000000 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_a_saga_message_goes_through_the_slr.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Sagas -{ - using System; - using EndpointTemplates; - using AcceptanceTesting; - using NUnit.Framework; - using Saga; - using ScenarioDescriptors; - - //repro for issue: https://github.com/NServiceBus/NServiceBus/issues/1020 - public class When_a_saga_message_goes_through_the_slr : NServiceBusAcceptanceTest - { - [Test] - public void Should_invoke_the_correct_handle_methods_on_the_saga() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSagaMessage { SomeId = Guid.NewGuid() }))) - .AllowExceptions() - .Done(c => c.SecondMessageProcessed) - .Repeat(r => r.For(Transports.Default)) - .Run(); - } - - public class Context : ScenarioContext - { - public bool SecondMessageProcessed { get; set; } - - - public int NumberOfTimesInvoked { get; set; } - } - - public class SagaEndpoint : EndpointConfigurationBuilder - { - public SagaEndpoint() - { - EndpointSetup(); - } - - public class TestSaga : Saga, IAmStartedByMessages,IHandleMessages - { - public Context Context { get; set; } - public void Handle(StartSagaMessage message) - { - Data.SomeId = message.SomeId; - - Bus.SendLocal(new SecondSagaMessage - { - SomeId = Data.SomeId - }); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s=>s.SomeId); - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s => s.SomeId); - } - - public void Handle(SecondSagaMessage message) - { - Context.NumberOfTimesInvoked++; - var shouldFail = Context.NumberOfTimesInvoked < 2; //1 FLR and 1 SLR - - if(shouldFail) - throw new Exception("Simulated exception"); - - Context.SecondMessageProcessed = true; - } - - } - - public class TestSagaData : IContainSagaData - { - public virtual Guid Id { get; set; } - public virtual string Originator { get; set; } - public virtual string OriginalMessageId { get; set; } - - [Unique] - public virtual Guid SomeId { get; set; } - } - } - - [Serializable] - public class StartSagaMessage : ICommand - { - public Guid SomeId { get; set; } - } - public class SecondSagaMessage : ICommand - { - public Guid SomeId { get; set; } - } - - public class SomeTimeout - { - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_an_endpoint_replies_to_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_an_endpoint_replies_to_a_saga.cs index 06e06381bcd..7246a8b4d54 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_an_endpoint_replies_to_a_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_an_endpoint_replies_to_a_saga.cs @@ -1,48 +1,54 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; using NUnit.Framework; - using Saga; - using ScenarioDescriptors; // Repro for issue https://github.com/NServiceBus/NServiceBus/issues/1277 to test the fix // making sure that the saga correlation still works. public class When_an_endpoint_replies_to_a_saga : NServiceBusAcceptanceTest { [Test] - public void Should_correlate_all_saga_messages_properly() + public async Task Should_correlate_all_saga_messages_properly() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSaga { DataId = Guid.NewGuid() }))) - .WithEndpoint() - .Done(c => c.DidSagaReplyMessageGetCorrelated) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.DidSagaReplyMessageGetCorrelated)) - .Run(); + var context = await Scenario.Define(c => { c.RunId = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, ctx) => session.SendLocal(new StartSaga + { + RunId = ctx.RunId + }))) + .WithEndpoint() + .Done(c => c.Done) + .Run(); + + Assert.IsTrue(context.DidSagaReplyMessageGetCorrelated); } public class Context : ScenarioContext { + public Guid RunId { get; set; } + public bool Done { get; set; } public bool DidSagaReplyMessageGetCorrelated { get; set; } } - public class EndpointThatHandlesAMessageFromSagaAndReplies : EndpointConfigurationBuilder + public class EndpointThatRepliesToSagaMessage : EndpointConfigurationBuilder { - public EndpointThatHandlesAMessageFromSagaAndReplies() + public EndpointThatRepliesToSagaMessage() { EndpointSetup(); } class DoSomethingHandler : IHandleMessages { - public IBus Bus { get; set; } - - public void Handle(DoSomething message) + public Task Handle(DoSomething message, IMessageHandlerContext context) { - Console.WriteLine("Received DoSomething command for DataId:{0} ... and responding with a reply", message.DataId); - Bus.Reply(new DoSomethingResponse { DataId = message.DataId }); + return context.Reply(new DoSomethingResponse + { + RunId = message.RunId + }); } } } @@ -51,56 +57,73 @@ public class EndpointThatHostsASaga : EndpointConfigurationBuilder { public EndpointThatHostsASaga() { - EndpointSetup() - .AddMapping(typeof (EndpointThatHandlesAMessageFromSagaAndReplies)); + EndpointSetup(c => c.EnableFeature()) + .AddMapping(typeof(EndpointThatRepliesToSagaMessage)); + } + public class SagaNotFound : IHandleSagaNotFound + { + public Context TestContext { get; set; } + + public Task Handle(object message, IMessageProcessingContext context) + { + var lostMessage = message as DoSomethingResponse; + if (lostMessage != null && lostMessage.RunId == TestContext.RunId) + { + TestContext.Done = true; + } + return Task.FromResult(0); + } } - public class Saga2 : Saga, IAmStartedByMessages, IHandleMessages + public class CorrelationTestSaga : Saga, + IAmStartedByMessages, + IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { - Console.Out.WriteLine("Saga2 sending DoSomething for DataId: {0}", message.DataId); - Data.DataId = message.DataId; - Bus.Send(new DoSomething { DataId = message.DataId }); + return context.Send(new DoSomething + { + RunId = message.RunId + }); } - public void Handle(DoSomethingResponse message) + public Task Handle(DoSomethingResponse message, IMessageHandlerContext context) { - Context.DidSagaReplyMessageGetCorrelated = message.DataId == Data.DataId; - Console.Out.WriteLine("Saga received DoSomethingResponse for DataId: {0} and MarkAsComplete", message.DataId); + TestContext.Done = true; + TestContext.DidSagaReplyMessageGetCorrelated = message.RunId == Data.RunId; MarkAsComplete(); + return Task.FromResult(0); } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.RunId).ToSaga(s => s.RunId); + mapper.ConfigureMapping(m => m.RunId).ToSaga(s => s.RunId); } - public class MySaga2Data : ContainSagaData + public class CorrelationTestSagaData : ContainSagaData { - [Unique] - public virtual Guid DataId { get; set; } + public virtual Guid RunId { get; set; } } } } - - [Serializable] public class StartSaga : ICommand { - public Guid DataId { get; set; } + public Guid RunId { get; set; } } public class DoSomething : ICommand { - public Guid DataId { get; set; } + public Guid RunId { get; set; } } public class DoSomethingResponse : IMessage { - public Guid DataId { get; set; } + public Guid RunId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_auto_correlated_property_is_changed.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_auto_correlated_property_is_changed.cs new file mode 100644 index 00000000000..fce2c2db730 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_auto_correlated_property_is_changed.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using EndpointTemplates; + using NUnit.Framework; + + [TestFixture] + public class When_auto_correlated_property_is_changed : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw() + { + var exception = Assert.ThrowsAsync(async () => + await Scenario.Define() + .WithEndpoint( + b => b.When(session => session.SendLocal(new StartSaga + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.FailedMessages.Any()) + .Run()) + .ExpectFailedMessages(); + + Assert.IsTrue(((Context)exception.ScenarioContext).ModifiedCorrelationProperty); + Assert.AreEqual(1, exception.FailedMessages.Count); + StringAssert.Contains( + "Changing the value of correlated properties at runtime is currently not supported", + exception.FailedMessages.Single().Exception.Message); + } + + public class Context : ScenarioContext + { + public bool ModifiedCorrelationProperty { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + public class CorrIdChangedSaga : Saga, + IAmStartedByMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSaga message, IMessageHandlerContext context) + { + Data.DataId = Guid.NewGuid(); + TestContext.ModifiedCorrelationProperty = true; + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); + } + + public class CorrIdChangedSagaData : ContainSagaData + { + public virtual Guid DataId { get; set; } + } + } + } + + public class StartSaga : ICommand + { + public Guid DataId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas.cs index 1f7db5cee53..90692b8047f 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas.cs @@ -1,61 +1,13 @@ - -namespace NServiceBus.AcceptanceTests.Sagas +namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NUnit.Framework; - using Saga; + using EndpointTemplates; + using Features; public class When_doing_request_response_between_sagas : NServiceBusAcceptanceTest { - [Test] - public void Should_autocorrelate_the_response_back_to_the_requesting_saga_from_the_first_handler() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new InitiateRequestingSaga()))) - .Done(c => c.DidRequestingSagaGetTheResponse) - .Run(new RunSettings { UseSeparateAppDomains = true }); - - Assert.True(context.DidRequestingSagaGetTheResponse); - } - - [Test] - public void Should_autocorrelate_the_response_back_to_the_requesting_saga_from_timeouts() - { - var context = new Context - { - ReplyFromTimeout = true - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new InitiateRequestingSaga()))) - .Done(c => c.DidRequestingSagaGetTheResponse) - .Run(new RunSettings { UseSeparateAppDomains = true, TestExecutionTimeout = TimeSpan.FromSeconds(15) }); - - Assert.True(context.DidRequestingSagaGetTheResponse); - } - - - [Test] - public void Should_autocorrelate_the_response_back_to_the_requesting_saga_from_handler_other_than_the_initiating_one() - { - var context = new Context - { - ReplyFromNonInitiatingHandler = true - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new InitiateRequestingSaga()))) - .Done(c => c.DidRequestingSagaGetTheResponse) - .Run(new RunSettings { UseSeparateAppDomains = true, TestExecutionTimeout = TimeSpan.FromSeconds(15) }); - - Assert.True(context.DidRequestingSagaGetTheResponse); - } - public class Context : ScenarioContext { public bool DidRequestingSagaGetTheResponse { get; set; } @@ -65,114 +17,120 @@ public class Context : ScenarioContext public class Endpoint : EndpointConfigurationBuilder { - public Endpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } - public class RequestingSaga : Saga, + public class RequestResponseRequestingSaga : Saga, IAmStartedByMessages, IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(InitiateRequestingSaga message) + public Task Handle(InitiateRequestingSaga message, IMessageHandlerContext context) { - Data.CorrIdForResponse = Guid.NewGuid(); //wont be needed in the future - - Bus.SendLocal(new RequestToRespondingSaga + return context.SendLocal(new RequestToRespondingSaga { SomeIdThatTheResponseSagaCanCorrelateBackToUs = Data.CorrIdForResponse //wont be needed in the future }); } - public void Handle(ResponseFromOtherSaga message) + public Task Handle(ResponseFromOtherSaga message, IMessageHandlerContext context) { - Context.DidRequestingSagaGetTheResponse = true; + TestContext.DidRequestingSagaGetTheResponse = true; + MarkAsComplete(); + + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - //if this line is un-commented the timeout and secondary handler tests will start to fail - // for more info and discussion see TBD + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.CorrIdForResponse); mapper.ConfigureMapping(m => m.SomeCorrelationId).ToSaga(s => s.CorrIdForResponse); } - public class RequestingSagaData : ContainSagaData + + public class RequestResponseRequestingSagaData : ContainSagaData { - [Unique] public virtual Guid CorrIdForResponse { get; set; } //wont be needed in the future } - } - public class RespondingSaga : Saga, + public class RequestResponseRespondingSaga : Saga, IAmStartedByMessages, - IHandleTimeouts, + IHandleTimeouts, IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(RequestToRespondingSaga message) + public async Task Handle(RequestToRespondingSaga message, IMessageHandlerContext context) { - if (Context.ReplyFromNonInitiatingHandler) + if (TestContext.ReplyFromNonInitiatingHandler) { - Data.CorrIdForRequest = message.SomeIdThatTheResponseSagaCanCorrelateBackToUs; //wont be needed in the future - Bus.SendLocal(new SendReplyFromNonInitiatingHandler { SagaIdSoWeCanCorrelate = Data.Id }); - return; + await context.SendLocal(new SendReplyFromNonInitiatingHandler + { + SagaIdSoWeCanCorrelate = Data.Id + }); } - if (Context.ReplyFromTimeout) + if (TestContext.ReplyFromTimeout) { - Data.CorrIdForRequest = message.SomeIdThatTheResponseSagaCanCorrelateBackToUs; //wont be needed in the future - RequestTimeout(TimeSpan.FromSeconds(1)); - return; + await RequestTimeout(context, TimeSpan.FromMilliseconds(1)); } // Both reply and reply to originator work here since the sender of the incoming message is the requesting saga // also note we don't set the correlation ID since auto correlation happens to work for this special case // where we reply from the first handler - Bus.Reply(new ResponseFromOtherSaga()); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - //this line is just needed so we can test the non initiating handler case - mapper.ConfigureMapping(m => m.SagaIdSoWeCanCorrelate).ToSaga(s => s.Id); + await context.Reply(new ResponseFromOtherSaga()); } - public class RespondingSagaData : ContainSagaData + public Task Handle(SendReplyFromNonInitiatingHandler message, IMessageHandlerContext context) { - [Unique] - public virtual Guid CorrIdForRequest { get; set; } + return SendReply(context); } - - public class DelayReply { } - - public void Timeout(DelayReply state) + public Task Timeout(DelayReply state, IMessageHandlerContext context) { - SendReply(); + return SendReply(context); } - public void Handle(SendReplyFromNonInitiatingHandler message) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - SendReply(); + mapper.ConfigureMapping(m => m.SomeIdThatTheResponseSagaCanCorrelateBackToUs).ToSaga(s => s.CorrIdForRequest); + //this line is just needed so we can test the non initiating handler case + mapper.ConfigureMapping(m => m.SagaIdSoWeCanCorrelate).ToSaga(s => s.CorrIdForRequest); } - void SendReply() + Task SendReply(IMessageHandlerContext context) { //reply to originator must be used here since the sender of the incoming message the timeoutmanager and not the requesting saga - ReplyToOriginator(new ResponseFromOtherSaga //change this line to Bus.Reply(new ResponseFromOtherSaga and see it fail + return ReplyToOriginator(context, new ResponseFromOtherSaga //change this line to Bus.Reply(new ResponseFromOtherSaga and see it fail { SomeCorrelationId = Data.CorrIdForRequest //wont be needed in the future }); } + + public class RequestResponseRespondingSagaData : ContainSagaData + { + public virtual Guid CorrIdForRequest { get; set; } + } + + public class DelayReply + { + } } } - public class InitiateRequestingSaga : ICommand { } + public class InitiateRequestingSaga : ICommand + { + public InitiateRequestingSaga() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; set; } + } public class RequestToRespondingSaga : ICommand { @@ -189,4 +147,4 @@ public class SendReplyFromNonInitiatingHandler : ICommand public Guid SagaIdSoWeCanCorrelate { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_first_handler_responding.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_first_handler_responding.cs new file mode 100644 index 00000000000..fdc309bfe67 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_first_handler_responding.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_doing_request_response_between_sagas_first_handler_responding : When_doing_request_response_between_sagas + { + [Test] + public async Task Should_autocorrelate_the_response_back_to_the_requesting_saga_from_the_first_handler() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new InitiateRequestingSaga()))) + .Done(c => c.DidRequestingSagaGetTheResponse) + .Run(); + + Assert.True(context.DidRequestingSagaGetTheResponse); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_response_from_noninitiating.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_response_from_noninitiating.cs new file mode 100644 index 00000000000..8e6993244dc --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_response_from_noninitiating.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_doing_request_response_between_sagas_response_from_noninitiating : When_doing_request_response_between_sagas + { + [Test] + public async Task Should_autocorrelate_the_response_back_to_the_requesting_saga_from_handler_other_than_the_initiating_one() + { + var context = await Scenario.Define(c => { c.ReplyFromNonInitiatingHandler = true; }) + .WithEndpoint(b => b.When(session => session.SendLocal(new InitiateRequestingSaga()))) + .Done(c => c.DidRequestingSagaGetTheResponse) + .Run(); + + Assert.True(context.DidRequestingSagaGetTheResponse); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_with_timeout.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_with_timeout.cs new file mode 100644 index 00000000000..963fe9726bb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_doing_request_response_between_sagas_with_timeout.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_doing_request_response_between_sagas_with_timeout : When_doing_request_response_between_sagas + { + [Test] + public async Task Should_autocorrelate_the_response_back_to_the_requesting_saga_from_timeouts() + { + var context = await Scenario.Define(c => { c.ReplyFromTimeout = true; }) + .WithEndpoint(b => b.When(session => session.SendLocal(new InitiateRequestingSaga()))) + .Done(c => c.DidRequestingSagaGetTheResponse) + .Run(TimeSpan.FromSeconds(15)); + + Assert.True(context.DidRequestingSagaGetTheResponse); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_forgetting_to_set_a_corr_property.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_forgetting_to_set_a_corr_property.cs new file mode 100644 index 00000000000..ece6dc5b163 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_forgetting_to_set_a_corr_property.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_forgetting_to_set_a_corr_property : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_matter() + { + var id = Guid.NewGuid(); + + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = id + }))) + .Done(c => c.Done) + .Run(); + + Assert.AreEqual(context.SomeId, id); + } + + public class Context : ScenarioContext + { + public Guid SomeId { get; set; } + public bool Done { get; set; } + } + + public class NullPropertyEndpoint : EndpointConfigurationBuilder + { + public NullPropertyEndpoint() + { + EndpointSetup(); + } + + public class NullCorrPropertySaga : Saga, IAmStartedByMessages + { + public Context Context { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + //oops I forgot Data.SomeId = message.SomeId + if (message.SecondMessage) + { + Context.SomeId = Data.SomeId; + Context.Done = true; + return Task.FromResult(0); + } + + return context.SendLocal(new StartSagaMessage + { + SomeId = message.SomeId, + SecondMessage = true + }); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + } + + public class NullCorrPropertySagaData : IContainSagaData + { + public virtual Guid SomeId { get; set; } + public virtual Guid Id { get; set; } + public virtual string Originator { get; set; } + public virtual string OriginalMessageId { get; set; } + } + } + + public class StartSagaMessage : ICommand + { + public Guid SomeId { get; set; } + public bool SecondMessage { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs index 41ea600e02e..cd3182130c9 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs @@ -1,104 +1,109 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Saga; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; using NUnit.Framework; public class When_message_has_a_saga_id : NServiceBusAcceptanceTest { [Test] - public void Should_not_start_a_new_saga_if_not_found() + public async Task Should_not_start_a_new_saga_if_not_found() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => { - var message = new MessageWithSagaId(); - - bus.SetMessageHeader(message, Headers.SagaId, Guid.NewGuid().ToString()); - bus.SetMessageHeader(message, Headers.SagaType, typeof(MySaga).AssemblyQualifiedName); - - bus.SendLocal(message); + var message = new MessageWithSagaId + { + DataId = Guid.NewGuid() + }; + var options = new SendOptions(); + + options.SetHeader(Headers.SagaId, Guid.NewGuid().ToString()); + options.SetHeader(Headers.SagaType, typeof(SagaEndpoint.MessageWithSagaIdSaga).AssemblyQualifiedName); + options.RouteToThisEndpoint(); + return session.Send(message, options); })) - .Done(c => c.OtherSagaStarted) + .Done(c => c.Done) .Run(); - Assert.False(context.NotFoundHandlerCalled); - Assert.True(context.OtherSagaStarted); + Assert.True(context.NotFoundHandlerCalled); Assert.False(context.MessageHandlerCalled); Assert.False(context.TimeoutHandlerCalled); } - class MySaga : Saga, IAmStartedByMessages, - IHandleTimeouts, - IHandleSagaNotFound + public class Context : ScenarioContext { - public Context Context { get; set; } - - public class SagaData : ContainSagaData - { - } + public bool NotFoundHandlerCalled { get; set; } + public bool MessageHandlerCalled { get; set; } + public bool TimeoutHandlerCalled { get; set; } + public bool OtherSagaStarted { get; set; } + public bool Done { get; set; } + } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() { - + EndpointSetup(c => c.EnableFeature()); } - public void Handle(MessageWithSagaId message) + public class MessageWithSagaIdSaga : Saga, + IAmStartedByMessages, + IHandleTimeouts, + IHandleSagaNotFound { - Context.MessageHandlerCalled = true; - } + public Context TestContext { get; set; } - public void Handle(object message) - { - Context.NotFoundHandlerCalled = true; - } + public Task Handle(MessageWithSagaId message, IMessageHandlerContext context) + { + TestContext.MessageHandlerCalled = true; + return Task.FromResult(0); + } - public void Timeout(MessageWithSagaId state) - { - Context.TimeoutHandlerCalled = true; - } - } + public Task Handle(object message, IMessageProcessingContext context) + { + TestContext.NotFoundHandlerCalled = true; + return Task.FromResult(0); + } - class MyOtherSaga : Saga, IAmStartedByMessages - { - public Context Context { get; set; } + public Task Timeout(MessageWithSagaId state, IMessageHandlerContext context) + { + TestContext.TimeoutHandlerCalled = true; + return Task.FromResult(0); + } - public void Handle(MessageWithSagaId message) - { - Context.OtherSagaStarted = true; - } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.DataId) + .ToSaga(s => s.DataId); + } + public class MessageWithSagaIdSagaData : ContainSagaData + { + public virtual Guid DataId { get; set; } + } } - public class SagaData : ContainSagaData + class MessageWithSagaIdHandler : IHandleMessages { - } - - } - + public Context TestContext { get; set; } - class Context : ScenarioContext - { - public bool NotFoundHandlerCalled { get; set; } - public bool MessageHandlerCalled { get; set; } - public bool TimeoutHandlerCalled { get; set; } - public bool OtherSagaStarted { get; set; } - } + public Task Handle(MessageWithSagaId message, IMessageHandlerContext context) + { + TestContext.Done = true; - public class SagaEndpoint : EndpointConfigurationBuilder - { - public SagaEndpoint() - { - EndpointSetup(); + return Task.FromResult(0); + } } } public class MessageWithSagaId : IMessage { + public Guid DataId { get; set; } } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_completes_the_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_completes_the_saga.cs index cbd2eb130a2..cbc4f120560 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_completes_the_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_completes_the_saga.cs @@ -1,72 +1,72 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; using ScenarioDescriptors; public class When_receiving_that_completes_the_saga : NServiceBusAcceptanceTest { [Test] - public void Should_hydrate_and_complete_the_existing_instance() + public Task Should_hydrate_and_complete_the_existing_instance() { - Scenario.Define(() => new Context { Id = Guid.NewGuid() }) - .WithEndpoint(b => + return Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => + { + b.When((session, context) => session.SendLocal(new StartSagaMessage + { + SomeId = context.Id + })); + b.When(context => context.StartSagaMessageReceived, (session, context) => + { + context.AddTrace("CompleteSagaMessage sent"); + + return session.SendLocal(new CompleteSagaMessage { - b.Given((bus, context) => bus.SendLocal(new StartSagaMessage { SomeId = context.Id })); - b.When(context => context.StartSagaMessageReceived, (bus, context) => - { - context.AddTrace("CompleteSagaMessage sent"); - - bus.SendLocal(new CompleteSagaMessage - { - SomeId = context.Id - }); - }); - }) - .Done(c => c.SagaCompleted) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.SagaCompleted)) - - .Run(); + SomeId = context.Id + }); + }); + }) + .Done(c => c.SagaCompleted) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.SagaCompleted)) + .Run(); } [Test] - public void Should_ignore_messages_afterwards() + public Task Should_ignore_messages_afterwards() { - var context = new Context - { - Id = Guid.NewGuid() - }; - - Scenario.Define(context) - .WithEndpoint(b => + return Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => { - b.Given((bus, c) => bus.SendLocal(new StartSagaMessage + b.When((session, c) => session.SendLocal(new StartSagaMessage { SomeId = c.Id })); - b.When(c => c.StartSagaMessageReceived, (bus, c) => + b.When(c => c.StartSagaMessageReceived, (session, c) => { c.AddTrace("CompleteSagaMessage sent"); - bus.SendLocal(new CompleteSagaMessage + return session.SendLocal(new CompleteSagaMessage { SomeId = c.Id }); }); - b.When(c => c.SagaCompleted, (bus, c) => bus.SendLocal(new AnotherMessage + b.When(c => c.SagaCompleted, (session, c) => session.SendLocal(new AnotherMessage { SomeId = c.Id })); }) .Done(c => c.AnotherMessageReceived) .Repeat(r => r.For(Transports.Default)) + .Should(c => + { + Assert.True(c.AnotherMessageReceived, "AnotherMessage should have been delivered to the handler outside the saga"); + Assert.False(c.SagaReceivedAnotherMessage, "AnotherMessage should not be delivered to the saga after completion"); + }) .Run(); - - Assert.True(context.AnotherMessageReceived, "AnotherMessage should have been delivered to the handler outside the saga"); - Assert.False(context.SagaReceivedAnotherMessage, "AnotherMessage should not be delivered to the saga after completion"); } public class Context : ScenarioContext @@ -78,44 +78,52 @@ public class Context : ScenarioContext public bool SagaReceivedAnotherMessage { get; set; } } - public class SagaEndpoint : EndpointConfigurationBuilder + public class ReceiveCompletesSagaEndpoint : EndpointConfigurationBuilder { - public SagaEndpoint() + public ReceiveCompletesSagaEndpoint() { - EndpointSetup(b => b.LoadMessageHandlers>()); + EndpointSetup(b => + { + b.EnableFeature(); + b.ExecuteTheseHandlersFirst(typeof(TestSaga10)); + b.LimitMessageProcessingConcurrencyTo(1); // This test only works if the endpoints processes messages sequentially + }); } - public class TestSaga : Saga, - IAmStartedByMessages, - IHandleMessages, - IHandleMessages, - IHandleSagaNotFound + public class TestSaga10 : Saga, + IAmStartedByMessages, + IHandleMessages, + IHandleMessages { public Context Context { get; set; } - public void Handle(StartSagaMessage message) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { Context.AddTrace("Saga started"); Data.SomeId = message.SomeId; Context.StartSagaMessageReceived = true; - } - public void Handle(CompleteSagaMessage message) - { - Context.AddTrace("CompleteSagaMessage received"); - MarkAsComplete(); - Context.SagaCompleted = true; + return Task.FromResult(0); } - public void Handle(AnotherMessage message) + public Task Handle(AnotherMessage message, IMessageHandlerContext context) { Context.AddTrace("AnotherMessage received"); Context.SagaReceivedAnotherMessage = true; + return Task.FromResult(0); + } + + public Task Handle(CompleteSagaMessage message, IMessageHandlerContext context) + { + Context.AddTrace("CompleteSagaMessage received"); + MarkAsComplete(); + Context.SagaCompleted = true; + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.ConfigureMapping(m => m.SomeId) .ToSaga(s => s.SomeId); @@ -124,34 +132,25 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper.ConfigureMapping(m => m.SomeId) .ToSaga(s => s.SomeId); } - - public void Handle(object message) - { - if (message is AnotherMessage) - { - return; - } - - throw new Exception("Unexpected 'saga not found' for message: " + message.GetType().Name); - } } - public class TestSagaData : IContainSagaData + public class TestSagaData10 : IContainSagaData { + public virtual Guid SomeId { get; set; } public virtual Guid Id { get; set; } public virtual string Originator { get; set; } public virtual string OriginalMessageId { get; set; } - [Unique] - public virtual Guid SomeId { get; set; } } } public class CompletionHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(AnotherMessage message) + + public Task Handle(AnotherMessage message, IMessageHandlerContext context) { Context.AnotherMessageReceived = true; + return Task.FromResult(0); } } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga.cs index dcfac93143e..d3941c62ce3 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga.cs @@ -1,46 +1,12 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; - using NUnit.Framework; - using Saga; - using ScenarioDescriptors; + using EndpointTemplates; public class When_receiving_that_should_start_a_saga : NServiceBusAcceptanceTest { - [Test] - public void Should_start_the_saga_and_call_messagehandlers() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSagaMessage()))) - .Done(context => context.InterceptingHandlerCalled && context.SagaStarted) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.InterceptingHandlerCalled, "The message handler should be called"); - Assert.True(c.SagaStarted, "The saga should have been started"); - }) - .Run(); - } - - - [Test] - public void Should_not_start_saga_if_a_interception_handler_has_been_invoked() - { - Scenario.Define(() => new SagaEndpointContext { InterceptSaga = true }) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSagaMessage()))) - .Done(context => context.InterceptingHandlerCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.InterceptingHandlerCalled, "The intercepting handler should be called"); - Assert.False(c.SagaStarted, "The saga should not have been started since the intercepting handler stops the pipeline"); - }) - .Run(); - } - - public class SagaEndpointContext : ScenarioContext { public bool InterceptingHandlerCalled { get; set; } @@ -50,47 +16,50 @@ public class SagaEndpointContext : ScenarioContext public bool InterceptSaga { get; set; } } - public class SagaEndpoint : EndpointConfigurationBuilder { public SagaEndpoint() { - EndpointSetup(b => b.LoadMessageHandlers>()); + EndpointSetup(b => b.ExecuteTheseHandlersFirst(typeof(InterceptingHandler))); } - public class TestSaga : Saga, IAmStartedByMessages + public class TestSaga03 : Saga, IAmStartedByMessages { public SagaEndpointContext Context { get; set; } - public void Handle(StartSagaMessage message) + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { Context.SagaStarted = true; + Data.SomeId = message.SomeId; + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - mapper.ConfigureMapping(m=>m.SomeId) - .ToSaga(s=>s.SomeId); + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); } - public class TestSagaData : ContainSagaData + public class TestSagaData03 : ContainSagaData { - public string SomeId { get; set; } + public virtual string SomeId { get; set; } } } - public class InterceptingHandler : IHandleMessages { - public SagaEndpointContext Context { get; set; } - - public IBus Bus { get; set; } + public SagaEndpointContext TestContext { get; set; } - public void Handle(StartSagaMessage message) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { - Context.InterceptingHandlerCalled = true; + TestContext.InterceptingHandlerCalled = true; - if (Context.InterceptSaga) - Bus.DoNotContinueDispatchingCurrentMessageToHandlers(); + if (TestContext.InterceptSaga) + { + context.DoNotContinueDispatchingCurrentMessageToHandlers(); + } + + return Task.FromResult(0); } } } @@ -100,8 +69,5 @@ public class StartSagaMessage : ICommand { public string SomeId { get; set; } } - - } - -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_with_interception.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_with_interception.cs new file mode 100644 index 00000000000..3c7a9704581 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_with_interception.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_receiving_that_should_start_a_saga_with_interception : When_receiving_that_should_start_a_saga + { + [Test] + public Task Should_not_start_saga_if_a_interception_handler_has_been_invoked() + { + return Scenario.Define(c => { c.InterceptSaga = true; }) + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid().ToString() + }))) + .Done(context => context.InterceptingHandlerCalled) + .Repeat(r => r.For(Transports.Default)) + .Should(c => + { + Assert.True(c.InterceptingHandlerCalled, "The intercepting handler should be called"); + Assert.False(c.SagaStarted, "The saga should not have been started since the intercepting handler stops the pipeline"); + }) + .Run(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_without_interception.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_without_interception.cs new file mode 100644 index 00000000000..565ac6e5273 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_receiving_that_should_start_a_saga_without_interception.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_receiving_that_should_start_a_saga_without_interception : When_receiving_that_should_start_a_saga + { + [Test] + public Task Should_start_the_saga_and_call_messagehandlers() + { + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid().ToString() + }))) + .Done(context => context.InterceptingHandlerCalled && context.SagaStarted) + .Repeat(r => r.For(Transports.Default)) + .Should(c => + { + Assert.True(c.InterceptingHandlerCalled, "The message handler should be called"); + Assert.True(c.SagaStarted, "The saga should have been started"); + }) + .Run(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_replies_to_message_published_by_a_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_replies_to_message_published_by_a_saga.cs index 7f0412cf80c..ed03d7587f2 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_replies_to_message_published_by_a_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_replies_to_message_published_by_a_saga.cs @@ -1,30 +1,29 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using Features; - using NServiceBus.Config; using NUnit.Framework; - using PubSub; - using Saga; + using Routing; using ScenarioDescriptors; public class When_replies_to_message_published_by_a_saga : NServiceBusAcceptanceTest { [Test] - public void Should_reply_to_a_message_published_by_a_saga() + public async Task Should_reply_to_a_message_published_by_a_saga() { - Scenario.Define() + await Scenario.Define() .WithEndpoint - (b => b.When(c => c.Subscribed, bus => bus.SendLocal(new StartSaga + (b => b.When(c => c.Subscribed, session => session.SendLocal(new StartSaga { DataId = Guid.NewGuid() })) ) - .WithEndpoint(b => b.Given((bus, context) => + .WithEndpoint(b => b.When(async (session, context) => { - bus.Subscribe(); + await session.Subscribe(); if (context.HasNativePubSubSupport) { context.Subscribed = true; @@ -46,25 +45,21 @@ public class ReplyEndpoint : EndpointConfigurationBuilder { public ReplyEndpoint() { - EndpointSetup(b => b.DisableFeature()) - .AddMapping(typeof(SagaEndpoint)) - .WithConfig(c => + EndpointSetup(b => { - c.MaxRetries = 0; + b.DisableFeature(); }) - .WithConfig(c => - { - c.Enabled = false; - }); + .AddMapping(typeof(SagaEndpoint)); } class DidSomethingHandler : IHandleMessages { - public IBus Bus { get; set; } - - public void Handle(DidSomething message) + public Task Handle(DidSomething message, IMessageHandlerContext context) { - Bus.Reply(new DidSomethingResponse { DataId = message.DataId }); + return context.Reply(new DidSomethingResponse + { + ReceivedDataId = message.DataId + }); } } } @@ -73,56 +68,58 @@ public class SagaEndpoint : EndpointConfigurationBuilder { public SagaEndpoint() { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + EndpointSetup(b => { - context.Subscribed = true; - })); + b.EnableFeature(); + b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; }); + }); } - public class Saga2 : Saga, IAmStartedByMessages, IHandleMessages + public class ReplyToPubMsgSaga : Saga, IAmStartedByMessages, IHandleMessages { public Context Context { get; set; } - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { Data.DataId = message.DataId; - Bus.Publish(new DidSomething { DataId = message.DataId }); + return context.Publish(new DidSomething + { + DataId = message.DataId + }); } - public void Handle(DidSomethingResponse message) + public Task Handle(DidSomethingResponse message, IMessageHandlerContext context) { - Context.DidSagaReplyMessageGetCorrelated = message.DataId == Data.DataId; + Context.DidSagaReplyMessageGetCorrelated = message.ReceivedDataId == Data.DataId; MarkAsComplete(); + return Task.FromResult(0); } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - public class MySaga2Data : ContainSagaData + public class ReplyToPubMsgSagaData : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } } } - - [Serializable] + public class StartSaga : ICommand { public Guid DataId { get; set; } } - [Serializable] public class DidSomething : IEvent { public Guid DataId { get; set; } } - [Serializable] public class DidSomethingResponse : IMessage { - public Guid DataId { get; set; } + public Guid ReceivedDataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_reply_from_a_finder.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_reply_from_a_finder.cs deleted file mode 100644 index 9232564ca9b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_reply_from_a_finder.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Sagas -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Saga; - using NUnit.Framework; - - [TestFixture] - public class When_reply_from_a_finder - { - [Test] - public void Should_be_received_by_handler() - { - var context = new Context - { - Id = Guid.NewGuid() - }; - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSagaMessage - { - Id = context.Id - }))) - .Done(c => c.HandlerFired) - .Run(); - - Assert.True(context.HandlerFired); - } - - public class Context : ScenarioContext - { - public bool HandlerFired { get; set; } - public Guid Id { get; set; } - } - - public class SagaEndpoint : EndpointConfigurationBuilder - { - public SagaEndpoint() - { - EndpointSetup(); - } - - class CustomFinder : IFindSagas.Using - { - public IBus Bus { get; set; } - public Context Context { get; set; } - - public TestSaga.SagaData FindBy(StartSagaMessage message) - { - Bus.Reply(new SagaNotFoundMessage - { - Id = Context.Id - }); - return null; - } - } - - public class TestSaga : Saga, - IAmStartedByMessages - { - public Context Context { get; set; } - - public void Handle(StartSagaMessage message) - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - - public class SagaData : ContainSagaData - { - } - } - - public class Handler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(SagaNotFoundMessage message) - { - if (Context.Id != message.Id) - { - return; - } - Context.HandlerFired = true; - } - } - } - - public class SagaNotFoundMessage : IMessage - { - public Guid Id { get; set; } - } - - public class StartSagaMessage : IMessage - { - public Guid Id { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_handles_unmapped_message.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_handles_unmapped_message.cs new file mode 100644 index 00000000000..9b814b06693 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_handles_unmapped_message.cs @@ -0,0 +1,123 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_saga_handles_unmapped_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw_on_unmapped_uncorrelated_msg() + { + var id = Guid.NewGuid(); + + var context = await Scenario.Define() + .WithEndpoint(b => + { + b.DoNotFailOnErrorMessages(); + + b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = id + })); + }) + .Done(c => c.MappedEchoReceived && (c.EchoReceived || c.FailedMessages.Any())) + .Run(); + + Assert.AreEqual(true, context.StartReceived); + Assert.AreEqual(true, context.OutboundReceived); + Assert.AreEqual(true, context.MappedEchoReceived); + Assert.AreEqual(false, context.EchoReceived); + Assert.AreEqual(1, context.FailedMessages.Count); + } + + public class Context : ScenarioContext + { + public bool StartReceived { get; set; } + public bool OutboundReceived { get; set; } + public bool EchoReceived { get; set; } + public bool MappedEchoReceived { get; set; } + } + + public class UnmappedMsgEndpoint : EndpointConfigurationBuilder + { + public UnmappedMsgEndpoint() + { + EndpointSetup(); + } + + public class UnmappedMsgSaga : Saga, + IAmStartedByMessages, + IHandleMessages, + IHandleMessages + { + public Context Context { get; set; } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + // No mapping for EchoMessage, so saga can't possibly be found + } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + Context.StartReceived = true; + return context.SendLocal(new OutboundMessage { SomeId = message.SomeId }); + } + + public Task Handle(MappedEchoMessage message, IMessageHandlerContext context) + { + Context.MappedEchoReceived = true; + return Task.FromResult(0); + } + + public Task Handle(EchoMessage message, IMessageHandlerContext context) + { + Context.EchoReceived = true; + return Task.FromResult(0); + } + } + + public class UnmappedMsgSagaData : ContainSagaData + { + public virtual Guid SomeId { get; set; } + } + + public class OutboundMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public async Task Handle(OutboundMessage message, IMessageHandlerContext context) + { + Context.OutboundReceived = true; + await context.SendLocal(new EchoMessage { SomeId = message.SomeId }); + await context.SendLocal(new MappedEchoMessage { SomeId = message.SomeId }); + } + } + } + + public class StartSagaMessage : ICommand + { + public Guid SomeId { get; set; } + } + + public class OutboundMessage : ICommand + { + public Guid SomeId { get; set; } + } + + public class EchoMessage : ICommand + { + public Guid SomeId { get; set; } + } + + public class MappedEchoMessage : ICommand + { + public Guid SomeId { get; set; } + } + } +} diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_has_a_non_empty_constructor.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_has_a_non_empty_constructor.cs index 95f2d8ec91b..c34724052b0 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_has_a_non_empty_constructor.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_has_a_non_empty_constructor.cs @@ -1,81 +1,82 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; - using Saga; using ScenarioDescriptors; public class When_saga_has_a_non_empty_constructor : NServiceBusAcceptanceTest { - static Guid IdThatSagaIsCorrelatedOn = Guid.NewGuid(); - [Test] - public void Should_hydrate_and_invoke_the_existing_instance() + public Task Should_hydrate_and_invoke_the_existing_instance() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => - { - bus.SendLocal(new StartSagaMessage { SomeId = IdThatSagaIsCorrelatedOn }); - bus.SendLocal(new OtherMessage { SomeId = IdThatSagaIsCorrelatedOn }); - })) - .Done(c => c.SecondMessageReceived) - .Repeat(r => r.For(Persistence.Default)) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = IdThatSagaIsCorrelatedOn + }))) + .Done(c => c.SecondMessageReceived) + .Repeat(r => r.For(Persistence.Default)) + .Run(); } + static Guid IdThatSagaIsCorrelatedOn = Guid.NewGuid(); + public class Context : ScenarioContext { public bool SecondMessageReceived { get; set; } - } - public class SagaEndpoint : EndpointConfigurationBuilder + public class NonEmptySagaCtorEndpt : EndpointConfigurationBuilder { - public SagaEndpoint() + public NonEmptySagaCtorEndpt() { - EndpointSetup( - - builder => builder.Transactions().DoNotWrapHandlersExecutionInATransactionScope()); + EndpointSetup(); } - public class TestSaga : Saga, - IAmStartedByMessages, IHandleMessages + public class TestSaga11 : Saga, + IAmStartedByMessages, + IHandleMessages { - Context context; - - // ReSharper disable once UnusedParameter.Local - public TestSaga(IBus bus,Context context) + public TestSaga11(Context testContext) { - this.context = context; + this.testContext = testContext; } - public void Handle(StartSagaMessage message) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { Data.SomeId = message.SomeId; + return context.SendLocal(new OtherMessage + { + SomeId = message.SomeId + }); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public Task Handle(OtherMessage message, IMessageHandlerContext context) { - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s=>s.SomeId); + testContext.SecondMessageReceived = true; + return Task.FromResult(0); } - public void Handle(OtherMessage message) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - context.SecondMessageReceived = true; + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); } + + Context testContext; } - public class TestSagaData : IContainSagaData + public class TestSagaData11 : IContainSagaData { + public virtual Guid SomeId { get; set; } public virtual Guid Id { get; set; } public virtual string Originator { get; set; } public virtual string OriginalMessageId { get; set; } - - [Unique] - public virtual Guid SomeId { get; set; } } } @@ -83,8 +84,8 @@ public class TestSagaData : IContainSagaData public class StartSagaMessage : ICommand { public Guid SomeId { get; set; } - } + [Serializable] public class OtherMessage : ICommand { diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_id_changed.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_id_changed.cs index 220cf9c726c..b72663b2239 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_id_changed.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_id_changed.cs @@ -1,101 +1,72 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using System.Diagnostics; - using EndpointTemplates; + using System.Linq; + using System.Threading.Tasks; using AcceptanceTesting; - using NServiceBus.Config; - using NServiceBus.Faults; - using NServiceBus.Features; + using AcceptanceTesting.Support; + using EndpointTemplates; using NUnit.Framework; - using Saga; - using ScenarioDescriptors; + [TestFixture] public class When_saga_id_changed : NServiceBusAcceptanceTest { [Test] public void Should_throw() { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint( - b => b.Given(bus => bus.SendLocal(new StartSaga - { - DataId = Guid.NewGuid() - }))) - .AllowExceptions() - .Done(c => c.ExceptionReceived) - .Repeat(r => r.For(Transports.Default)) - .Run(); + var exception = Assert.ThrowsAsync(async () => + await Scenario.Define() + .WithEndpoint( + b => b.When(session => session.SendLocal(new StartSaga + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.FailedMessages.Any()) + .Run()) + .ExpectFailedMessages(); - Debug.WriteLine(context.ExceptionMessage, "A modification of IContainSagaData.Id has been detected. This property is for infrastructure purposes only and should not be modified. SagaType: " + typeof(Endpoint.MySaga)); + Assert.That(exception.FailedMessages, Has.Count.EqualTo(1)); + var failedMessage = exception.FailedMessages.Single(); + Assert.That(((Context) exception.ScenarioContext).MessageId, Is.EqualTo(failedMessage.MessageId), "Message should be moved to errorqueue"); + Assert.That(failedMessage.Exception.Message, Contains.Substring("A modification of IContainSagaData.Id has been detected. This property is for infrastructure purposes only and should not be modified. SagaType:")); } public class Context : ScenarioContext { - public bool ExceptionReceived { get; set; } - public string ExceptionMessage { get; set; } + public string MessageId { get; set; } } public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(b => - { - b.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); - b.DisableFeature(); - }) - .WithConfig(c => - { - c.MaxRetries = 0; - }); + EndpointSetup(); } - public class MySaga : Saga, + public class SagaIdChangedSaga : Saga, IAmStartedByMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { - Data.DataId = message.DataId; Data.Id = Guid.NewGuid(); + TestContext.MessageId = context.MessageId; + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - public class MySagaData : ContainSagaData + public class SagaIdChangedSagaData : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } - - } - class CustomFaultManager : IManageMessageFailures - { - public Context Context { get; set; } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - Context.ExceptionMessage = e.Message; - Context.ExceptionReceived = true; - } - - public void Init(Address address) - { - } } } - [Serializable] public class StartSaga : ICommand { public Guid DataId { get; set; } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_is_mapped_to_complex_expression.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_is_mapped_to_complex_expression.cs index dd5850a5bcb..123a392b43b 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_is_mapped_to_complex_expression.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_is_mapped_to_complex_expression.cs @@ -1,79 +1,91 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; - using Saga; - using ScenarioDescriptors; public class When_saga_is_mapped_to_complex_expression : NServiceBusAcceptanceTest { - [Test] - public void Should_hydrate_and_invoke_the_existing_instance() + public async Task Should_hydrate_and_invoke_the_existing_instance() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => - { - bus.SendLocal(new StartSagaMessage { Key = "Part1_Part2"}); - bus.SendLocal(new OtherMessage { Part1 = "Part1", Part2 = "Part2" }); - })) - .Done(c => c.SecondMessageReceived) - .Repeat(r => r.For(Persistence.Default)) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new StartSagaMessage + { + Key = "Part1_Part2" + })) + .When(c => c.FirstMessageReceived, session => session.SendLocal(new OtherMessage + { + Part1 = "Part1", + Part2 = "Part2" + }))) + .Done(c => c.SecondMessageReceived) + .Run(); + + Assert.IsTrue(context.SecondMessageReceived); } public class Context : ScenarioContext { + public bool FirstMessageReceived { get; set; } public bool SecondMessageReceived { get; set; } + public Guid SagaId { get; set; } } public class SagaEndpoint : EndpointConfigurationBuilder { public SagaEndpoint() { - EndpointSetup( - - builder => builder.Transactions().DoNotWrapHandlersExecutionInATransactionScope()); + //note: the concurrency checks for the InMemory persister doesn't seem to work so limiting to 1 for now + EndpointSetup(c => c.LimitMessageProcessingConcurrencyTo(1)); } - public class TestSaga : Saga, - IAmStartedByMessages, IHandleMessages + public class TestSaga02 : Saga, + IAmStartedByMessages, IAmStartedByMessages { public Context Context { get; set; } - public void Handle(StartSagaMessage message) + + public Task Handle(OtherMessage message, IMessageHandlerContext context) { - Data.KeyValue = message.Key; + Assert.AreEqual(Context.SagaId, Data.Id, "Existing instance should be found"); + Context.SecondMessageReceived = true; + return Task.FromResult(0); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { - mapper.ConfigureMapping(m => m.Part1 + "_" + m.Part2) - .ToSaga(s => s.KeyValue); + Context.FirstMessageReceived = true; + Context.SagaId = Data.Id; + return Task.FromResult(0); } - public void Handle(OtherMessage message) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - Context.SecondMessageReceived = true; + mapper.ConfigureMapping(m => m.Key) + .ToSaga(s => s.KeyValue); + + mapper.ConfigureMapping(m => m.Part1 + "_" + m.Part2) + .ToSaga(s => s.KeyValue); } } - public class TestSagaData : IContainSagaData + public class TestSagaData02 : IContainSagaData { + public virtual string KeyValue { get; set; } public virtual Guid Id { get; set; } public virtual string Originator { get; set; } public virtual string OriginalMessageId { get; set; } - public virtual string KeyValue { get; set; } } } - [Serializable] public class StartSagaMessage : ICommand { public string Key { get; set; } } - [Serializable] + public class OtherMessage : ICommand { public string Part2 { get; set; } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_message_goes_through_delayed_retries.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_message_goes_through_delayed_retries.cs new file mode 100644 index 00000000000..0c645bde840 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_message_goes_through_delayed_retries.cs @@ -0,0 +1,113 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + //repro for issue: https://github.com/NServiceBus/NServiceBus/issues/1020 + public class When_saga_message_goes_through_delayed_retries : NServiceBusAcceptanceTest + { + [Test] + public Task Should_invoke_the_correct_handle_methods_on_the_saga() + { + return Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid() + }))) + .Done(c => c.SecondMessageProcessed) + .Repeat(r => r.For(Transports.Default)) + .Run(); + } + + public class Context : ScenarioContext + { + public bool SecondMessageProcessed { get; set; } + public int NumberOfTimesInvoked { get; set; } + } + + public class SagaMessageThroughDelayedRetryEndpoint : EndpointConfigurationBuilder + { + public SagaMessageThroughDelayedRetryEndpoint() + { + EndpointSetup(b => + { + b.EnableFeature(); + var recoverability = b.Recoverability(); + recoverability.Delayed(settings => + { + settings.NumberOfRetries(1); + settings.TimeIncrease(TimeSpan.FromMilliseconds(1)); + }); + }); + } + + public class TestSaga09 : Saga, + IAmStartedByMessages, + IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + Data.SomeId = message.SomeId; + + return context.SendLocal(new SecondSagaMessage + { + SomeId = Data.SomeId + }); + } + + public Task Handle(SecondSagaMessage message, IMessageHandlerContext context) + { + TestContext.NumberOfTimesInvoked++; + + if (TestContext.NumberOfTimesInvoked < 2) + { + throw new SimulatedException(); + } + + TestContext.SecondMessageProcessed = true; + + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + } + + public class TestSagaData09 : IContainSagaData + { + public virtual Guid SomeId { get; set; } + public virtual Guid Id { get; set; } + public virtual string Originator { get; set; } + public virtual string OriginalMessageId { get; set; } + } + } + + [Serializable] + public class StartSagaMessage : ICommand + { + public Guid SomeId { get; set; } + } + + public class SecondSagaMessage : ICommand + { + public Guid SomeId { get; set; } + } + + public class SomeTimeout + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_started_concurrently.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_started_concurrently.cs index f9914d225c5..06e41f873b6 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_saga_started_concurrently.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_saga_started_concurrently.cs @@ -2,44 +2,31 @@ { using System; using System.Threading.Tasks; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Config; - using NServiceBus.Saga; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; public class When_saga_started_concurrently : NServiceBusAcceptanceTest { [Test] - public void Should_start_single_saga() + public async Task Should_start_single_saga() { - var context = new Context - { - SomeId = Guid.NewGuid().ToString() - }; - - Scenario.Define(context) + var context = await Scenario.Define(c => { c.SomeId = Guid.NewGuid().ToString(); }) .WithEndpoint(b => { - b.When(bus => + b.When((session, ctx) => { - Parallel.Invoke(() => + var t1 = session.SendLocal(new StartMessageOne { - bus.SendLocal(new StartMessageOne - { - SomeId = context.SomeId - }); - }, () => + SomeId = ctx.SomeId + }); + var t2 = session.SendLocal(new StartMessageTwo { - bus.SendLocal(new StartMessageTwo - { - SomeId = context.SomeId - } - ); + SomeId = ctx.SomeId }); + return Task.WhenAll(t1, t2); }); }) - .AllowExceptions() .Done(c => c.PlacedSagaId != Guid.Empty && c.BilledSagaId != Guid.Empty) .Run(); @@ -60,16 +47,11 @@ class ConcurrentHandlerEndpoint : EndpointConfigurationBuilder { public ConcurrentHandlerEndpoint() { - EndpointSetup(b => { }) - .WithConfig(c => - { - c.MaxRetries = 3; - c.MaximumConcurrencyLevel = 2; - }) - .WithConfig(c => - { - c.Enabled = false; - }); + EndpointSetup(b => + { + b.LimitMessageProcessingConcurrencyTo(2); + b.Recoverability().Immediate(immediate => immediate.NumberOfRetries(3)); + }); } class ConcurrentlyStartedSaga : Saga, @@ -78,28 +60,26 @@ class ConcurrentlyStartedSaga : Saga, { public Context Context { get; set; } - public void Handle(StartMessageOne message) + public async Task Handle(StartMessageOne message, IMessageHandlerContext context) { - Data.OrderId = message.SomeId; Data.Placed = true; - Bus.SendLocal(new SuccessfulProcessing + await context.SendLocal(new SuccessfulProcessing { SagaId = Data.Id, Type = nameof(StartMessageOne) }); - CheckForCompletion(); + CheckForCompletion(context); } - public void Handle(StartMessageTwo message) + public async Task Handle(StartMessageTwo message, IMessageHandlerContext context) { - Data.OrderId = message.SomeId; Data.Billed = true; - Bus.SendLocal(new SuccessfulProcessing + await context.SendLocal(new SuccessfulProcessing { SagaId = Data.Id, Type = nameof(StartMessageTwo) }); - CheckForCompletion(); + CheckForCompletion(context); } protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) @@ -108,7 +88,7 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper(msg => msg.SomeId).ToSaga(saga => saga.OrderId); } - void CheckForCompletion() + void CheckForCompletion(IMessageHandlerContext context) { if (!Data.Billed || !Data.Placed) { @@ -121,7 +101,6 @@ void CheckForCompletion() class ConcurrentlyStartedSagaData : ContainSagaData { - [Unique] public virtual string OrderId { get; set; } public virtual bool Placed { get; set; } public virtual bool Billed { get; set; } @@ -132,7 +111,7 @@ class LogSuccessfulHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(SuccessfulProcessing message) + public Task Handle(SuccessfulProcessing message, IMessageHandlerContext context) { if (message.Type == nameof(StartMessageOne)) { @@ -146,23 +125,22 @@ public void Handle(SuccessfulProcessing message) { throw new Exception("Unknown type"); } + + return Task.FromResult(0); } } } - [Serializable] class StartMessageOne : ICommand { public string SomeId { get; set; } } - [Serializable] class StartMessageTwo : ICommand { public string SomeId { get; set; } } - [Serializable] class SuccessfulProcessing : ICommand { public string Type { get; set; } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_sagas_cant_be_found.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_sagas_cant_be_found.cs index 4b613d32a1f..988d80069f4 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_sagas_cant_be_found.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_sagas_cant_be_found.cs @@ -1,35 +1,39 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; using NUnit.Framework; - using Saga; public class When_sagas_cant_be_found : NServiceBusAcceptanceTest { [Test] - public void IHandleSagaNotFound_only_called_once() + public async Task IHandleSagaNotFound_only_called_once() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MessageToSaga()))) - .Done(c => c.Done) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToSaga + { + Id = Guid.NewGuid() + }))) + .Done(c => c.Done) + .Run(); Assert.AreEqual(1, context.TimesFired); } [Test] - public void IHandleSagaNotFound_not_called_if_second_saga_is_executed() + public async Task IHandleSagaNotFound_not_called_if_second_saga_is_executed() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.SendLocal(new MessageToSaga()))) - .Done(c => c.Done) - .Run(); + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MessageToSaga + { + Id = Guid.NewGuid() + }))) + .Done(c => c.Done) + .Run(); Assert.AreEqual(0, context.TimesFired); } @@ -44,76 +48,93 @@ public class ReceiverWithSagas : EndpointConfigurationBuilder { public ReceiverWithSagas() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } public class MessageToSagaHandler : IHandleMessages { - public IBus Bus { get; set; } - - public void Handle(MessageToSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { - Bus.Defer(TimeSpan.FromSeconds(10), new FinishMessage()); + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromMilliseconds(1)); + options.RouteToThisEndpoint(); + + return context.Send(new FinishMessage(), options); } } - public class FinishHandler: IHandleMessages + public class FinishHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(FinishMessage message) + public Task Handle(FinishMessage message, IMessageHandlerContext context) { + // This acts as a safe guard to abort the test earlier Context.Done = true; + return Task.FromResult(0); } } - public class Saga1 : Saga, IAmStartedByMessages, IHandleMessages + public class CantBeFoundSaga1 : Saga, IAmStartedByMessages, IHandleMessages { - - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { + Data.MessageId = message.Id; + return Task.FromResult(0); } - public void Handle(MessageToSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { + return Task.FromResult(0); } - public class Saga1Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class CantBeFoundSaga1Data : ContainSagaData { + public virtual Guid MessageId { get; set; } } } - public class Saga2 : Saga, IAmStartedByMessages, IHandleMessages + public class CantBeFoundSaga2 : Saga, IAmStartedByMessages, IHandleMessages { - - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { + Data.MessageId = message.Id; + return Task.FromResult(0); } - public void Handle(MessageToSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { + return Task.FromResult(0); } - public class Saga2Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class CantBeFoundSaga2Data : ContainSagaData { + public virtual Guid MessageId { get; set; } } } public class SagaNotFound : IHandleSagaNotFound { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(object message) + public Task Handle(object message, IMessageProcessingContext context) { - Context.TimesFired++; + TestContext.TimesFired++; + TestContext.Done = true; + return Task.FromResult(0); } } } @@ -122,24 +143,23 @@ public class ReceiverWithOrderedSagas : EndpointConfigurationBuilder { public ReceiverWithOrderedSagas() { - EndpointSetup(); - } - - class EnsureOrdering : ISpecifyMessageHandlerOrdering - { - public void SpecifyOrder(Order order) + EndpointSetup(c => { - order.Specify(First.Then()); - } + c.EnableFeature(); + c.ExecuteTheseHandlersFirst(typeof(ReceiverWithOrderedSagasSaga1), typeof(ReceiverWithOrderedSagasSaga2)); + }); } public class MessageToSagaHandler : IHandleMessages { - public IBus Bus { get; set; } - - public void Handle(MessageToSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { - Bus.Defer(TimeSpan.FromSeconds(10), new FinishMessage()); + var options = new SendOptions(); + + options.DelayDeliveryWith(TimeSpan.FromMilliseconds(1)); + options.RouteToThisEndpoint(); + + return context.Send(new FinishMessage(), options); } } @@ -147,65 +167,84 @@ public class FinishHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(FinishMessage message) + public Task Handle(FinishMessage message, IMessageHandlerContext context) { + // This acts as a safe guard to abort the test earlier Context.Done = true; + return Task.FromResult(0); } } - public class Saga1 : Saga, IAmStartedByMessages, IHandleMessages + public class ReceiverWithOrderedSagasSaga1 : Saga, IAmStartedByMessages, IHandleMessages { - - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { + Data.MessageId = message.Id; + return Task.FromResult(0); } - public void Handle(MessageToSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { + return Task.FromResult(0); } - public class Saga1Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class ReceiverWithOrderedSagasSaga1Data : ContainSagaData { + public virtual Guid MessageId { get; set; } } } - public class Saga2 : Saga, IHandleMessages, IAmStartedByMessages + public class ReceiverWithOrderedSagasSaga2 : Saga, IHandleMessages, IAmStartedByMessages { + public Context Context { get; set; } - public void Handle(StartSaga message) + public Task Handle(MessageToSaga message, IMessageHandlerContext context) { + Data.MessageId = message.Id; + Context.Done = true; + return Task.FromResult(0); } - public void Handle(MessageToSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { + Data.MessageId = message.Id; + return Task.FromResult(0); } - public class Saga2Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); + mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.MessageId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class ReceiverWithOrderedSagasSaga2Data : ContainSagaData { + public virtual Guid MessageId { get; set; } } } public class SagaNotFound : IHandleSagaNotFound { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(object message) + public Task Handle(object message, IMessageProcessingContext context) { - Context.TimesFired++; + TestContext.TimesFired++; + return Task.FromResult(0); } } } + [Serializable] public class StartSaga : ICommand { + public Guid Id { get; set; } } [Serializable] @@ -216,6 +255,7 @@ public class FinishMessage : ICommand [Serializable] public class MessageToSaga : ICommand { + public Guid Id { get; set; } } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_handle.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_handle.cs index c2afb5f736e..1a0782561d3 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_handle.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_handle.cs @@ -1,24 +1,27 @@ - -namespace NServiceBus.AcceptanceTests.Sagas +namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; using ScenarioDescriptors; public class When_sending_from_a_saga_handle : NServiceBusAcceptanceTest { [Test] - public void Should_match_different_saga() + public Task Should_match_different_saga() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSaga1 { DataId = Guid.NewGuid() }))) - .Done(c => c.DidSaga2ReceiveMessage) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.DidSaga2ReceiveMessage)) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSaga1 + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.DidSaga2ReceiveMessage) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.DidSaga2ReceiveMessage)) + .Run(); } public class Context : ScenarioContext @@ -28,82 +31,82 @@ public class Context : ScenarioContext public class Endpoint : EndpointConfigurationBuilder { - public Endpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } - public class Saga1 : Saga, IAmStartedByMessages, IHandleMessages + public class TwoSaga1Saga1 : Saga, IAmStartedByMessages, IHandleMessages { - public Context Context { get; set; } - - public void Handle(StartSaga1 message) + public Task Handle(StartSaga1 message, IMessageHandlerContext context) { - var dataId = Guid.NewGuid(); - Data.DataId = dataId; - Bus.SendLocal(new MessageSaga1WillHandle - { - DataId = dataId - }); + Data.DataId = message.DataId; + return context.SendLocal(new MessageSaga1WillHandle + { + DataId = message.DataId + }); } - public void Handle(MessageSaga1WillHandle message) + public async Task Handle(MessageSaga1WillHandle message, IMessageHandlerContext context) { - Bus.SendLocal(new StartSaga2()); + await context.SendLocal(new StartSaga2 + { + DataId = message.DataId + }); MarkAsComplete(); } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - } - public class Saga1Data : ContainSagaData + public class TwoSaga1Saga1Data : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } - - public class Saga2 : Saga, IAmStartedByMessages + public class TwoSaga1Saga2 : Saga, IAmStartedByMessages { public Context Context { get; set; } - public void Handle(StartSaga2 message) + public Task Handle(StartSaga2 message, IMessageHandlerContext context) { + Data.DataId = message.DataId; Context.DidSaga2ReceiveMessage = true; + + return Task.FromResult(0); } - public class Saga2Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class TwoSaga1Saga2Data : ContainSagaData { + public virtual Guid DataId { get; set; } } } - } - [Serializable] public class StartSaga1 : ICommand { public Guid DataId { get; set; } } - [Serializable] public class StartSaga2 : ICommand { + public Guid DataId { get; set; } } + public class MessageSaga1WillHandle : IMessage { public Guid DataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_timeout.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_timeout.cs index 15182783e28..af8ae6aa4d8 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_timeout.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_sending_from_a_saga_timeout.cs @@ -1,23 +1,27 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; using ScenarioDescriptors; public class When_sending_from_a_saga_timeout : NServiceBusAcceptanceTest { [Test] - public void Should_match_different_saga() + public Task Should_match_different_saga() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSaga1()))) - .Done(c => c.DidSaga2ReceiveMessage) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.DidSaga2ReceiveMessage)) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSaga1 + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.DidSaga2ReceiveMessage) + .Repeat(r => r.For(Transports.Default)) + .Should(c => Assert.True(c.DidSaga2ReceiveMessage)) + .Run(); } public class Context : ScenarioContext @@ -27,64 +31,76 @@ public class Context : ScenarioContext public class Endpoint : EndpointConfigurationBuilder { - public Endpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } - public class Saga1 : Saga, IAmStartedByMessages, IHandleTimeouts + public class SendFromTimeoutSaga1 : Saga, + IAmStartedByMessages, + IHandleTimeouts { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga1 message) + public Task Handle(StartSaga1 message, IMessageHandlerContext context) { - RequestTimeout(TimeSpan.FromSeconds(1), new Saga1Timeout()); + Data.DataId = message.DataId; + return RequestTimeout(context, TimeSpan.FromMilliseconds(1), new Saga1Timeout()); } - public void Timeout(Saga1Timeout state) + public async Task Timeout(Saga1Timeout state, IMessageHandlerContext context) { - Bus.SendLocal(new StartSaga2()); + await context.SendLocal(new StartSaga2 + { + DataId = Data.DataId + }); MarkAsComplete(); } - public class Saga1Data : ContainSagaData + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class SendFromTimeoutSaga1Data : ContainSagaData { + public virtual Guid DataId { get; set; } } } - public class Saga2 : Saga, IAmStartedByMessages + public class SendFromTimeoutSaga2 : Saga, IAmStartedByMessages { public Context Context { get; set; } - public void Handle(StartSaga2 message) + public Task Handle(StartSaga2 message, IMessageHandlerContext context) { + Data.DataId = message.DataId; Context.DidSaga2ReceiveMessage = true; + return Task.FromResult(0); } - public class Saga2Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class SendFromTimeoutSaga2Data : ContainSagaData { + public virtual Guid DataId { get; set; } } } - } - [Serializable] public class StartSaga1 : ICommand { + public Guid DataId { get; set; } } [Serializable] public class StartSaga2 : ICommand { + public Guid DataId { get; set; } } public class Saga1Timeout : IMessage diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_base_event_from_other_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_base_event_from_other_saga.cs index e27a7d2cf53..f571c796310 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_base_event_from_other_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_base_event_from_other_saga.cs @@ -1,34 +1,34 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using Features; using NUnit.Framework; - using PubSub; - using Saga; + using Routing; using ScenarioDescriptors; //Repro for #1323 public class When_started_by_base_event_from_other_saga : NServiceBusAcceptanceTest { - [Test] - public void Should_start_the_saga_when_set_up_to_start_for_the_base_event() + public async Task Should_start_the_saga_when_set_up_to_start_for_the_base_event() { - Scenario.Define() + await Scenario.Define() .WithEndpoint(b => b.When(c => c.IsEventSubscriptionReceived, - bus => - bus.Publish(m => { m.DataId = Guid.NewGuid(); })) + session => { return session.Publish(m => { m.DataId = Guid.NewGuid(); }); }) ) .WithEndpoint( - b => b.Given((bus, context) => + b => b.When(async (session, context) => { - bus.Subscribe(); + await session.Subscribe(); if (context.HasNativePubSubSupport) + { context.IsEventSubscriptionReceived = true; + } })) .Done(c => c.DidSagaComplete) .Repeat(r => r.For(Transports.Default)) @@ -48,7 +48,7 @@ public Publisher() { EndpointSetup(b => b.OnEndpointSubscribed((s, context) => { - context.AddTrace("Subscription received for " + s.SubscriberReturnAddress.Queue); + context.AddTrace("Subscription received for " + s.SubscriberReturnAddress); context.IsEventSubscriptionReceived = true; })); } @@ -58,29 +58,34 @@ public class SagaThatIsStartedByABaseEvent : EndpointConfigurationBuilder { public SagaThatIsStartedByABaseEvent() { - EndpointSetup(c => c.DisableFeature()) + EndpointSetup(c => + { + c.EnableFeature(); + c.DisableFeature(); + }) .AddMapping(typeof(Publisher)); } - public class SagaStartedByBaseEvent : Saga, IAmStartedByMessages + public class SagaStartedByBaseEvent : Saga, IAmStartedByMessages { public SagaContext Context { get; set; } - public void Handle(BaseEvent message) + public Task Handle(BaseEvent message, IMessageHandlerContext context) { Data.DataId = message.DataId; MarkAsComplete(); Context.DidSagaComplete = true; + return Task.FromResult(0); } - public class SagaData : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - [Unique] - public virtual Guid DataId { get; set; } + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class SagaStartedByBaseEventSagaData : ContainSagaData { + public virtual Guid DataId { get; set; } } } } @@ -93,7 +98,6 @@ public class StartSaga : ICommand public interface SomethingHappenedEvent : BaseEvent { - } public interface BaseEvent : IEvent @@ -101,4 +105,4 @@ public interface BaseEvent : IEvent Guid DataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_event_from_another_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_event_from_another_saga.cs index 9450e9b298c..9badea2a1d2 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_event_from_another_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_started_by_event_from_another_saga.cs @@ -1,36 +1,37 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; using Features; using NUnit.Framework; - using PubSub; - using Saga; + using Routing; using ScenarioDescriptors; //Repro for #1323 public class When_started_by_event_from_another_saga : NServiceBusAcceptanceTest { [Test] - public void Should_start_the_saga_and_request_a_timeout() + public async Task Should_start_the_saga_and_request_a_timeout() { - Scenario.Define() + await Scenario.Define() .WithEndpoint(b => b.When(c => c.IsEventSubscriptionReceived, - bus => - bus.SendLocal(new StartSaga - { - DataId = Guid.NewGuid() - })) + session => session.SendLocal(new StartSaga + { + DataId = Guid.NewGuid() + })) ) .WithEndpoint( - b => b.Given((bus, context) => + b => b.When(async (session, context) => { - bus.Subscribe(); + await session.Subscribe(); if (context.HasNativePubSubSupport) + { context.IsEventSubscriptionReceived = true; + } })) .Done(c => c.DidSaga1Complete && c.DidSaga2Complete) .Repeat(r => r.For(Transports.Default)) @@ -49,44 +50,48 @@ public class SagaThatPublishesAnEvent : EndpointConfigurationBuilder { public SagaThatPublishesAnEvent() { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + EndpointSetup(b => { - context.IsEventSubscriptionReceived = true; - })); + b.EnableFeature(); + b.OnEndpointSubscribed((s, context) => { context.IsEventSubscriptionReceived = true; }); + }); } - public class Saga1 : Saga, IAmStartedByMessages, IHandleTimeouts + public class EventFromOtherSaga1 : Saga, + IAmStartedByMessages, + IHandleTimeouts { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga message) + public async Task Handle(StartSaga message, IMessageHandlerContext context) { Data.DataId = message.DataId; //Publish the event, which will start the second saga - Bus.Publish(m => { m.DataId = message.DataId; }); + await context.Publish(m => { m.DataId = message.DataId; }); //Request a timeout - RequestTimeout(TimeSpan.FromSeconds(5)); + await RequestTimeout(context, TimeSpan.FromMilliseconds(1)); } - public void Timeout(Timeout1 state) + public Task Timeout(Timeout1 state, IMessageHandlerContext context) { MarkAsComplete(); - Context.DidSaga1Complete = true; + TestContext.DidSaga1Complete = true; + return Task.FromResult(0); } - public class Saga1Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - [Unique] - public virtual Guid DataId { get; set; } + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - public class Timeout1 + public class EventFromOtherSaga1Data : ContainSagaData { + public virtual Guid DataId { get; set; } } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class Timeout1 { } } @@ -96,40 +101,45 @@ public class SagaThatIsStartedByTheEvent : EndpointConfigurationBuilder { public SagaThatIsStartedByTheEvent() { - EndpointSetup(c => c.DisableFeature()) + EndpointSetup(c => + { + c.EnableFeature(); + c.DisableFeature(); + }) .AddMapping(typeof(SagaThatPublishesAnEvent)); - } - public class Saga2 : Saga, IAmStartedByMessages, IHandleTimeouts + public class EventFromOtherSaga2 : Saga, + IAmStartedByMessages, + IHandleTimeouts { public Context Context { get; set; } - public void Handle(SomethingHappenedEvent message) + public Task Handle(SomethingHappenedEvent message, IMessageHandlerContext context) { Data.DataId = message.DataId; - //Request a timeout - RequestTimeout(TimeSpan.FromSeconds(5)); + return RequestTimeout(context, TimeSpan.FromMilliseconds(1)); } - public void Timeout(Saga2Timeout state) + public Task Timeout(Saga2Timeout state, IMessageHandlerContext context) { MarkAsComplete(); Context.DidSaga2Complete = true; + return Task.FromResult(0); } - public class Saga2Data : ContainSagaData + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - [Unique] - public virtual Guid DataId { get; set; } + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - public class Saga2Timeout + public class EventFromOtherSaga2Data : ContainSagaData { + public virtual Guid DataId { get; set; } } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public class Saga2Timeout { } } @@ -143,7 +153,6 @@ public class StartSaga : ICommand public interface SomethingHappenedEvent : BaseEvent { - } public interface BaseEvent : IEvent @@ -151,4 +160,4 @@ public interface BaseEvent : IEvent Guid DataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_timeout_hit_not_found_saga.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_timeout_hit_not_found_saga.cs index 5b19f2a14fe..8f1c567a804 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_timeout_hit_not_found_saga.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_timeout_hit_not_found_saga.cs @@ -1,24 +1,27 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; using NUnit.Framework; - using Saga; public class When_timeout_hit_not_found_saga : NServiceBusAcceptanceTest { [Test] - public void Should_not_fire_notfound_for_tm() + public async Task Should_not_fire_notfound_for_tm() { - var context = Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new StartSaga()))) + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSaga + { + DataId = Guid.NewGuid() + }))) .Done(c => c.NotFoundHandlerCalledForRegularMessage) .Run(); - Assert.False(context.NotFoundHandlerCalledForTimeout); - } public class Context : ScenarioContext @@ -31,64 +34,80 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } - public class MySaga : Saga, - IAmStartedByMessages, IHandleSagaNotFound, - IHandleTimeouts, + public class TimeoutHitsNotFoundSaga : Saga, + IAmStartedByMessages, + IHandleSagaNotFound, + IHandleTimeouts, IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga message) + public async Task Handle(StartSaga message, IMessageHandlerContext context) { + Data.DataId = message.DataId; + //this will cause the message to be delivered right away - RequestTimeout(TimeSpan.Zero); - Bus.SendLocal(new SomeOtherMessage()); + await RequestTimeout(context, TimeSpan.Zero); + await context.SendLocal(new SomeOtherMessage + { + DataId = Guid.NewGuid() + }); MarkAsComplete(); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - - public class MySagaData : ContainSagaData + public Task Handle(SomeOtherMessage message, IMessageHandlerContext context) { - public virtual Guid DataId { get; set; } + return Task.FromResult(0); } - public class MyTimeout { } - - public void Handle(object message) + public Task Handle(object message, IMessageProcessingContext context) { if (message is SomeOtherMessage) { - Context.NotFoundHandlerCalledForRegularMessage = true; + TestContext.NotFoundHandlerCalledForRegularMessage = true; } - if (message is MyTimeout) { - Context.NotFoundHandlerCalledForTimeout = true; + TestContext.NotFoundHandlerCalledForTimeout = true; } - + return Task.FromResult(0); } - public void Handle(SomeOtherMessage message) + public Task Timeout(MyTimeout state, IMessageHandlerContext context) { + return Task.FromResult(0); + } + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); + mapper.ConfigureMapping(m => m.DataId).ToSaga(s => s.DataId); } - public void Timeout(MyTimeout state) + public class TimeoutHitsNotFoundSagaData : ContainSagaData { + public virtual Guid DataId { get; set; } + } + public class MyTimeout + { } } } - public class StartSaga : IMessage { } - public class SomeOtherMessage : IMessage { } + public class StartSaga : IMessage + { + public Guid DataId { get; set; } + } + + public class SomeOtherMessage : IMessage + { + public Guid DataId { get; set; } + } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_two_sagas_subscribe_to_the_same_event.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_two_sagas_subscribe_to_the_same_event.cs index 31a666fd3d3..b94352f6496 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_two_sagas_subscribe_to_the_same_event.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_two_sagas_subscribe_to_the_same_event.cs @@ -1,39 +1,40 @@ - -namespace NServiceBus.AcceptanceTests.Sagas +namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using PubSub; - using Saga; + using Routing; using ScenarioDescriptors; // Repro for issue https://github.com/NServiceBus/NServiceBus/issues/1277 public class When_two_sagas_subscribe_to_the_same_event : NServiceBusAcceptanceTest { [Test] - public void Should_invoke_all_handlers_on_all_sagas() + public Task Should_invoke_all_handlers_on_all_sagas() { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.Subscribed, bus => bus.SendLocal(new StartSaga2 - { - DataId = Guid.NewGuid() - })) - ) - .WithEndpoint(b => b.Given((bus, context) => + return Scenario.Define() + .WithEndpoint(b => b.When((session, context) => + { + if (context.HasNativePubSubSupport) { - if (context.HasNativePubSubSupport) - { - context.Subscribed = true; - context.AddTrace("EndpointThatHandlesAMessageAndPublishesEvent is now subscribed (at least we have asked the broker to be subscribed)"); - } + context.Subscribed = true; + context.AddTrace("EndpointThatHandlesAMessageAndPublishesEvent is now subscribed (at least we have asked the broker to be subscribed)"); + } + return Task.FromResult(0); + })) + .WithEndpoint(b => + b.When(c => c.Subscribed, session => session.SendLocal(new StartSaga2 + { + DataId = Guid.NewGuid() })) - .Done(c => c.DidSaga1EventHandlerGetInvoked && c.DidSaga2EventHandlerGetInvoked) - .Repeat(r => r.For()) // exclude the brokers since c.Subscribed won't get set for them - .Should(c => Assert.True(c.DidSaga1EventHandlerGetInvoked && c.DidSaga2EventHandlerGetInvoked)) - .Run(); + ) + .Done(c => c.DidSaga1EventHandlerGetInvoked && c.DidSaga2EventHandlerGetInvoked) + .Repeat(r => r.For()) // exclude the brokers since c.Subscribed won't get set for them + .Should(c => Assert.True(c.DidSaga1EventHandlerGetInvoked && c.DidSaga2EventHandlerGetInvoked)) + .Run(); } public class Context : ScenarioContext @@ -47,20 +48,22 @@ public class Publisher : EndpointConfigurationBuilder { public Publisher() { - EndpointSetup(b => b.OnEndpointSubscribed((s, context) => + EndpointSetup(b => { - context.Subscribed = true; - })); + b.EnableFeature(); + b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; }); + }); } class OpenGroupCommandHandler : IHandleMessages { - public IBus Bus { get; set; } - - public void Handle(OpenGroupCommand message) + public Task Handle(OpenGroupCommand message, IMessageHandlerContext context) { - Console.WriteLine("Received OpenGroupCommand for DataId:{0} ... and publishing GroupPendingEvent", message.DataId); - Bus.Publish(new GroupPendingEvent { DataId = message.DataId }); + Console.WriteLine("Received OpenGroupCommand for RunId:{0} ... and publishing GroupPendingEvent", message.DataId); + return context.Publish(new GroupPendingEvent + { + DataId = message.DataId + }); } } } @@ -69,28 +72,34 @@ public class SagaEndpoint : EndpointConfigurationBuilder { public SagaEndpoint() { - EndpointSetup() + EndpointSetup(c => c.EnableFeature()) .AddMapping(typeof(Publisher)) .AddMapping(typeof(Publisher)); } - public class Saga1 : Saga, IAmStartedByMessages, IHandleMessages + public class Saga1 : Saga, + IAmStartedByMessages, + IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(GroupPendingEvent message) + public Task Handle(GroupPendingEvent message, IMessageHandlerContext context) { - Data.DataId = message.DataId; - Console.Out.WriteLine("Saga1 received GroupPendingEvent for DataId: {0}", message.DataId); - Bus.SendLocal(new CompleteSaga1Now { DataId = message.DataId }); + Console.Out.WriteLine("Saga1 received GroupPendingEvent for RunId: {0}", message.DataId); + return context.SendLocal(new CompleteSaga1Now + { + DataId = message.DataId + }); } - public void Handle(CompleteSaga1Now message) + public Task Handle(CompleteSaga1Now message, IMessageHandlerContext context) { - Console.Out.WriteLine("Saga1 received CompleteSaga1Now for DataId:{0} and MarkAsComplete", message.DataId); - Context.DidSaga1EventHandlerGetInvoked = true; + Console.Out.WriteLine("Saga1 received CompleteSaga1Now for RunId:{0} and MarkAsComplete", message.DataId); + TestContext.DidSaga1EventHandlerGetInvoked = true; MarkAsComplete(); + + return Task.FromResult(0); } protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) @@ -101,29 +110,31 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper m public class MySaga1Data : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } - } - public class Saga2 : Saga, IAmStartedByMessages, IHandleMessages + public class Saga2 : Saga, + IAmStartedByMessages, + IHandleMessages { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga2 message) + public Task Handle(StartSaga2 message, IMessageHandlerContext context) { - var dataId = Guid.NewGuid(); - Console.Out.WriteLine("Saga2 sending OpenGroupCommand for DataId: {0}", dataId); - Data.DataId = dataId; - Bus.Send(new OpenGroupCommand { DataId = dataId }); + Console.Out.WriteLine("Saga2 sending OpenGroupCommand for RunId: {0}", Data.DataId); + return context.Send(new OpenGroupCommand + { + DataId = Data.DataId + }); } - public void Handle(GroupPendingEvent message) + public Task Handle(GroupPendingEvent message, IMessageHandlerContext context) { - Context.DidSaga2EventHandlerGetInvoked = true; - Console.Out.WriteLine("Saga2 received GroupPendingEvent for DataId: {0} and MarkAsComplete", message.DataId); + TestContext.DidSaga2EventHandlerGetInvoked = true; + Console.Out.WriteLine("Saga2 received GroupPendingEvent for RunId: {0} and MarkAsComplete", message.DataId); MarkAsComplete(); + return Task.FromResult(0); } protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) @@ -134,7 +145,6 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper m public class MySaga2Data : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } } @@ -162,4 +172,4 @@ public class CompleteSaga1Now : ICommand public Guid DataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_updating_existing_correlation_property.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_updating_existing_correlation_property.cs new file mode 100644 index 00000000000..fb5c927a583 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_updating_existing_correlation_property.cs @@ -0,0 +1,87 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using EndpointTemplates; + using NUnit.Framework; + + public class When_updating_existing_correlation_property : NServiceBusAcceptanceTest + { + [Test] + public void Should_blow_up() + { + var exception = Assert.ThrowsAsync(async () => + await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid() + }))) + .Done(c => c.FailedMessages.Any()) + .Run()) + .ExpectFailedMessages(); + + Assert.IsTrue(((Context)exception.ScenarioContext).ModifiedCorrelationProperty); + Assert.AreEqual(1, exception.FailedMessages.Count); + StringAssert.Contains( + "Changing the value of correlated properties at runtime is currently not supported", + exception.FailedMessages.Single().Exception.Message); + } + + public class Context : ScenarioContext + { + public bool ModifiedCorrelationProperty { get; set; } + } + + public class ChangePropertyEndpoint : EndpointConfigurationBuilder + { + public ChangePropertyEndpoint() + { + EndpointSetup(); + } + + public class ChangeCorrPropertySaga : Saga, IAmStartedByMessages + { + public Context TestContext { get; set; } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + if (message.SecondMessage) + { + Data.SomeId = Guid.NewGuid(); //this is not allowed + TestContext.ModifiedCorrelationProperty = true; + return Task.FromResult(0); + } + + return context.SendLocal(new StartSagaMessage + { + SecondMessage = true, + SomeId = Data.SomeId + }); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + } + + public class ChangeCorrPropertySagaData : IContainSagaData + { + public virtual Guid SomeId { get; set; } + public virtual Guid Id { get; set; } + public virtual string Originator { get; set; } + public virtual string OriginalMessageId { get; set; } + } + } + + public class StartSagaMessage : ICommand + { + public Guid SomeId { get; set; } + public bool SecondMessage { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_using_ReplyToOriginator.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_using_ReplyToOriginator.cs index 922fe691c3b..1847ea70caf 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_using_ReplyToOriginator.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_using_ReplyToOriginator.cs @@ -1,20 +1,22 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; public class When_using_ReplyToOriginator : NServiceBusAcceptanceTest { [Test] - public void Should_set_Reply_as_messageintent() + public async Task Should_set_Reply_as_messageintent() { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new InitiateRequestingSaga()))) + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new InitiateRequestingSaga + { + SomeCorrelationId = Guid.NewGuid() + }))) .Done(c => c.Done) .Run(); @@ -29,40 +31,39 @@ public class Context : ScenarioContext public class Endpoint : EndpointConfigurationBuilder { - public Endpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } public class RequestingSaga : Saga, IAmStartedByMessages, IHandleMessages { - public Context Context { get; set; } - - public void Handle(InitiateRequestingSaga message) + public Task Handle(InitiateRequestingSaga message, IMessageHandlerContext context) { - Data.CorrIdForResponse = Guid.NewGuid(); //wont be needed in the future + Data.CorrIdForResponse = message.SomeCorrelationId; //wont be needed in the future - Bus.SendLocal(new AnotherRequest + return context.SendLocal(new AnotherRequest { SomeCorrelationId = Data.CorrIdForResponse //wont be needed in the future }); } - public void Handle(AnotherRequest message) + public async Task Handle(AnotherRequest message, IMessageHandlerContext context) { - ReplyToOriginator(new MyReplyToOriginator()); + await ReplyToOriginator(context, new MyReplyToOriginator()); MarkAsComplete(); } protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - //if this line is un-commented the timeout and secondary handler tests will start to fail - // for more info and discussion see TBD - mapper.ConfigureMapping(m => m.SomeCorrelationId).ToSaga(s => s.CorrIdForResponse); + mapper.ConfigureMapping(m => m.SomeCorrelationId) + .ToSaga(s => s.CorrIdForResponse); + mapper.ConfigureMapping(m => m.SomeCorrelationId) + .ToSaga(s => s.CorrIdForResponse); } + public class RequestingSagaData : ContainSagaData { public virtual Guid CorrIdForResponse { get; set; } //wont be needed in the future @@ -71,18 +72,21 @@ public class RequestingSagaData : ContainSagaData class MyReplyToOriginatorHandler : IHandleMessages { - public Context Context { get; set; } - public IBus Bus { get; set; } + public Context TestContext { get; set; } - public void Handle(MyReplyToOriginator message) + public Task Handle(MyReplyToOriginator message, IMessageHandlerContext context) { - Context.Intent = (MessageIntentEnum)Enum.Parse(typeof(MessageIntentEnum), Bus.CurrentMessageContext.Headers[Headers.MessageIntent]); - Context.Done = true; + TestContext.Intent = (MessageIntentEnum) Enum.Parse(typeof(MessageIntentEnum), context.MessageHeaders[Headers.MessageIntent]); + TestContext.Done = true; + return Task.FromResult(0); } } } - public class InitiateRequestingSaga : ICommand { } + public class InitiateRequestingSaga : ICommand + { + public Guid SomeCorrelationId { get; set; } + } public class AnotherRequest : ICommand { diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_using_a_received_message_for_timeout.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_using_a_received_message_for_timeout.cs index 9d08146991b..25c222f78d2 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_using_a_received_message_for_timeout.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_using_a_received_message_for_timeout.cs @@ -1,20 +1,24 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; public class When_using_a_received_message_for_timeout : NServiceBusAcceptanceTest { [Test] - public void Timeout_should_be_received_after_expiration() + public Task Timeout_should_be_received_after_expiration() { - Scenario.Define(() => new Context {Id = Guid.NewGuid()}) - .WithEndpoint(g => g.Given(bus=>bus.SendLocal(new StartSagaMessage()))) - .Done(c => c.TimeoutReceived) - .Run(); + return Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(g => g.When(session => session.SendLocal(new StartSagaMessage + { + SomeId = Guid.NewGuid() + }))) + .Done(c => c.TimeoutReceived) + .Run(); } public class Context : ScenarioContext @@ -26,44 +30,43 @@ public class Context : ScenarioContext public bool TimeoutReceived { get; set; } } - public class SagaEndpoint : EndpointConfigurationBuilder + public class ReceiveMessageForTimeoutEndpoint : EndpointConfigurationBuilder { - public SagaEndpoint() + public ReceiveMessageForTimeoutEndpoint() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } - public class TestSaga : Saga, IAmStartedByMessages, IHandleTimeouts + public class TestSaga01 : Saga, IAmStartedByMessages, IHandleTimeouts { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSagaMessage message) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { Data.SomeId = message.SomeId; - RequestTimeout(TimeSpan.FromMilliseconds(100), message); + return RequestTimeout(context, TimeSpan.FromMilliseconds(100), message); } - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public Task Timeout(StartSagaMessage message, IMessageHandlerContext context) { - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s => s.SomeId); + MarkAsComplete(); + TestContext.TimeoutReceived = true; + return Task.FromResult(0); } - public void Timeout(StartSagaMessage message) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - Context.TimeoutReceived = true; - MarkAsComplete(); + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); } } - public class TestSagaData : IContainSagaData + public class TestSagaData01 : IContainSagaData { + public virtual Guid SomeId { get; set; } public virtual Guid Id { get; set; } public virtual string Originator { get; set; } public virtual string OriginalMessageId { get; set; } - - [Unique] - public virtual Guid SomeId { get; set; } } } diff --git a/src/NServiceBus.AcceptanceTests/Sagas/When_using_contain_saga_data.cs b/src/NServiceBus.AcceptanceTests/Sagas/When_using_contain_saga_data.cs index bbe40e713c4..f326cfc3f11 100644 --- a/src/NServiceBus.AcceptanceTests/Sagas/When_using_contain_saga_data.cs +++ b/src/NServiceBus.AcceptanceTests/Sagas/When_using_contain_saga_data.cs @@ -1,57 +1,59 @@ namespace NServiceBus.AcceptanceTests.Sagas { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; - using Saga; - using ScenarioDescriptors; - // Repro for #SB-191 public class When_using_contain_saga_data : NServiceBusAcceptanceTest { [Test] - public void Should_handle_timeouts_properly() + public async Task Should_handle_timeouts_properly() { - Scenario.Define() - .WithEndpoint( - b => b.Given(bus => bus.SendLocal(new StartSaga {DataId = Guid.NewGuid()}))) - .Done(c => c.DidAllSagaInstancesReceiveTimeouts) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.DidAllSagaInstancesReceiveTimeouts)) - .Run(); + var context = await Scenario.Define() + .WithEndpoint( + b => b.When(session => session.SendLocal(new StartSaga + { + DataId = Guid.NewGuid() + }))) + .Done(c => c.TimeoutReceived) + .Run(); + + Assert.True(context.TimeoutReceived); } public class Context : ScenarioContext { - public bool DidAllSagaInstancesReceiveTimeouts { get; set; } + public bool TimeoutReceived { get; set; } } public class EndpointThatHostsASaga : EndpointConfigurationBuilder { public EndpointThatHostsASaga() { - EndpointSetup(); + EndpointSetup(config => config.EnableFeature()); } public class MySaga : Saga, - IAmStartedByMessages, - IHandleTimeouts + IAmStartedByMessages, + IHandleTimeouts { - public Context Context { get; set; } + public Context TestContext { get; set; } - public void Handle(StartSaga message) + public Task Handle(StartSaga message, IMessageHandlerContext context) { Data.DataId = message.DataId; - RequestTimeout(TimeSpan.FromSeconds(5), new TimeHasPassed()); + return RequestTimeout(context, TimeSpan.FromMilliseconds(1), new TimeHasPassed()); } - public void Timeout(TimeHasPassed state) + public Task Timeout(TimeHasPassed state, IMessageHandlerContext context) { MarkAsComplete(); - - Context.DidAllSagaInstancesReceiveTimeouts = true; + TestContext.TimeoutReceived = true; + return Task.FromResult(0); } protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) @@ -61,21 +63,18 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper ma public class MySagaData : ContainSagaData { - [Unique] public virtual Guid DataId { get; set; } } public class TimeHasPassed { } - } } - [Serializable] - public class StartSaga : ICommand + public class StartSaga : IMessage { public Guid DataId { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/when_receiving_multiple_timeouts.cs b/src/NServiceBus.AcceptanceTests/Sagas/when_receiving_multiple_timeouts.cs new file mode 100644 index 00000000000..35d22a4661e --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/when_receiving_multiple_timeouts.cs @@ -0,0 +1,158 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; + using NUnit.Framework; + + public class when_receiving_multiple_timeouts : NServiceBusAcceptanceTest + { + // realted to NSB issue #1819 + [Test] + public async Task It_should_not_invoke_SagaNotFound_handler() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new StartSaga1 + { + ContextId = c.Id + }))) + .Done(c => (c.Saga1TimeoutFired && c.Saga2TimeoutFired) || c.SagaNotFound) + .Run(TimeSpan.FromSeconds(60)); + + Assert.IsFalse(context.SagaNotFound); + Assert.IsTrue(context.Saga1TimeoutFired); + Assert.IsTrue(context.Saga2TimeoutFired); + } + + public class Context : ScenarioContext + { + public Guid Id { get; set; } + public bool Saga1TimeoutFired { get; set; } + public bool Saga2TimeoutFired { get; set; } + public bool SagaNotFound { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.EnableFeature(); + c.ExecuteTheseHandlersFirst(typeof(CatchAllMessageHandler)); + c.Recoverability().Immediate(immediate => immediate.NumberOfRetries(5)); + }); + } + + public class MultiTimeoutsSaga1 : Saga, + IAmStartedByMessages, + IHandleTimeouts, + IHandleTimeouts + { + public Context TestContext { get; set; } + + public async Task Handle(StartSaga1 message, IMessageHandlerContext context) + { + if (message.ContextId != TestContext.Id) + { + return; + } + + Data.ContextId = message.ContextId; + + await RequestTimeout(context, TimeSpan.FromMilliseconds(1), new Saga1Timeout + { + ContextId = TestContext.Id + }); + await RequestTimeout(context, TimeSpan.FromMilliseconds(1), new Saga2Timeout + { + ContextId = TestContext.Id + }); + } + + public Task Timeout(Saga1Timeout state, IMessageHandlerContext context) + { + if (state.ContextId == TestContext.Id) + { + TestContext.Saga1TimeoutFired = true; + } + + if (TestContext.Saga1TimeoutFired && TestContext.Saga2TimeoutFired) + { + MarkAsComplete(); + } + return Task.FromResult(0); + } + + public Task Timeout(Saga2Timeout state, IMessageHandlerContext context) + { + if (state.ContextId == TestContext.Id) + { + TestContext.Saga2TimeoutFired = true; + } + + if (TestContext.Saga1TimeoutFired && TestContext.Saga2TimeoutFired) + { + MarkAsComplete(); + } + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.ContextId) + .ToSaga(s => s.Id); + } + + public class MultiTimeoutsSaga1Data : ContainSagaData + { + public virtual Guid ContextId { get; set; } + } + } + + public class SagaNotFound : IHandleSagaNotFound + { + public Context TestContext { get; set; } + + public Task Handle(object message, IMessageProcessingContext context) + { + if (((dynamic) message).ContextId != TestContext.Id) + { + return Task.FromResult(0); + } + + TestContext.SagaNotFound = true; + + return Task.FromResult(0); + } + } + + public class CatchAllMessageHandler : IHandleMessages + { + public Task Handle(object message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + } + } + + [Serializable] + public class StartSaga1 : ICommand + { + public Guid ContextId { get; set; } + } + + public class Saga1Timeout + { + public Guid ContextId { get; set; } + } + + public class Saga2Timeout + { + public Guid ContextId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Sagas/when_reply_from_saga_not_found_handler.cs b/src/NServiceBus.AcceptanceTests/Sagas/when_reply_from_saga_not_found_handler.cs new file mode 100644 index 00000000000..c52e9102064 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Sagas/when_reply_from_saga_not_found_handler.cs @@ -0,0 +1,112 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NServiceBus.Sagas; + using NUnit.Framework; + + public class when_reply_from_saga_not_found_handler : NServiceBusAcceptanceTest + { + // related to NSB issue #2044 + [Test] + public async Task It_should_invoke_message_handler() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new MessageToSaga()))) + .WithEndpoint() + .Done(c => c.ReplyReceived) + .Run(); + + Assert.IsTrue(context.ReplyReceived); + } + + public class Context : ScenarioContext + { + public bool ReplyReceived { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup() + .AddMapping(typeof(ReceiverWithSaga)); + } + + public class ReplyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Reply message, IMessageHandlerContext context) + { + Context.ReplyReceived = true; + + return Task.FromResult(0); + } + } + } + + public class ReceiverWithSaga : EndpointConfigurationBuilder + { + public ReceiverWithSaga() + { + EndpointSetup(c => c.EnableFeature()); + } + + public class NotFoundHandlerSaga1 : Saga, IAmStartedByMessages, IHandleMessages + { + public Task Handle(StartSaga1 message, IMessageHandlerContext context) + { + Data.ContextId = message.ContextId; + return Task.FromResult(0); + } + + public Task Handle(MessageToSaga message, IMessageHandlerContext context) + { + return Task.FromResult(0); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.ContextId) + .ToSaga(s => s.ContextId); + mapper.ConfigureMapping(m => m.ContextId) + .ToSaga(s => s.ContextId); + } + + public class NotFoundHandlerSaga1Data : ContainSagaData + { + public virtual Guid ContextId { get; set; } + } + } + + public class SagaNotFound : IHandleSagaNotFound + { + public Task Handle(object message, IMessageProcessingContext context) + { + return context.Reply(new Reply()); + } + } + } + + [Serializable] + public class StartSaga1 : ICommand + { + public Guid ContextId { get; set; } + } + + [Serializable] + public class MessageToSaga : ICommand + { + public Guid ContextId { get; set; } + } + + [Serializable] + public class Reply : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Satellites/When_a_message_is_available.cs b/src/NServiceBus.AcceptanceTests/Satellites/When_a_message_is_available.cs new file mode 100644 index 00000000000..efebe1af3fb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Satellites/When_a_message_is_available.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.AcceptanceTests.Satellites +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using Transport; + + public class When_a_message_is_available : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_the_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(Endpoint.MySatelliteFeature.Address, new MyMessage()))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.True(context.MessageReceived); + // In the future we want the transport transaction to be an explicit + // concept in the persisters API as well. Adding transport transaction + // to the context will not be necessary at that point. + // See GitHub issue #4047 for more background information. + Assert.True(context.TransportTransactionAddedToContext); + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public bool TransportTransactionAddedToContext { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(); + } + + public class MySatelliteFeature : Feature + { + public MySatelliteFeature() + { + EnableByDefault(); + } + + protected override void Setup(FeatureConfigurationContext context) + { + var satelliteLogicalAddress = context.Settings.LogicalAddress().CreateQualifiedAddress("MySatellite"); + var satelliteAddress = context.Settings.GetTransportAddress(satelliteLogicalAddress); + + context.AddSatelliteReceiver("Test satellite", satelliteAddress, TransportTransactionMode.ReceiveOnly, PushRuntimeSettings.Default, + (c, ec) => RecoverabilityAction.MoveToError(c.Failed.ErrorQueue), + (builder, pushContext) => + { + var testContext = builder.Build(); + testContext.MessageReceived = true; + testContext.TransportTransactionAddedToContext = ReferenceEquals(pushContext.Context.Get(), pushContext.TransportTransaction); + return Task.FromResult(true); + }); + + Address = satelliteAddress; + } + + public static string Address; + } + } + + class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled.cs b/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled.cs deleted file mode 100644 index 55ed2556eff..00000000000 --- a/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace NServiceBus.AcceptanceTests.ScaleOut -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NUnit.Framework; - - public class When_individualization_is_enabled : NServiceBusAcceptanceTest - { - const string discriminator = "-something"; - - - [Test] - public void Should_use_the_configured_differentiator() - { - var context = Scenario.Define() - .WithEndpoint().Done(c =>c.EndpointsStarted) - .Run(); - - - Assert.True(context.Address.Contains("-something"),context.Address + " should contain the discriminator " + discriminator); - - } - - public class Context : ScenarioContext - { - public string Address { get; set; } - } - - public class IndividualizedEndpoint : EndpointConfigurationBuilder - { - - public IndividualizedEndpoint() - { - EndpointSetup(c=>c.ScaleOut().UniqueQueuePerEndpointInstance(discriminator)); - } - - class AddressSpy : IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - - public Configure Configure { get; set; } - - public void Start() - { - Context.Address = Configure.LocalAddress.ToString(); - } - - public void Stop() - { - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled_for_msmq.cs b/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled_for_msmq.cs deleted file mode 100644 index 4dab94258ee..00000000000 --- a/src/NServiceBus.AcceptanceTests/ScaleOut/When_individualization_is_enabled_for_msmq.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace NServiceBus.AcceptanceTests.ScaleOut -{ - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NServiceBus.Settings; - using NUnit.Framework; - - public class When_individualization_is_enabled_for_msmq : NServiceBusAcceptanceTest - { - [Test] - public void Should_be_a_no_op_discriminator() - { - Scenario.Define() - .WithEndpoint().Done(c =>c.EndpointsStarted) - .Repeat(r => r.For()) - .Should(c=>Assert.AreEqual(c.EndpointName,c.Address.Split('@').First())) - .Run(); - } - - public class Context : ScenarioContext - { - public string Address { get; set; } - public string EndpointName { get; set; } - } - - public class IndividualizedEndpoint : EndpointConfigurationBuilder - { - - public IndividualizedEndpoint() - { - EndpointSetup(c=>c.ScaleOut().UniqueQueuePerEndpointInstance()); - } - - class AddressSpy : IWantToRunWhenBusStartsAndStops - { - public Context Context { get; set; } - - public Configure Configure { get; set; } - - public ReadOnlySettings ReadOnlySettings { get; set; } - - public void Start() - { - Context.Address = Configure.LocalAddress.ToString(); - Context.EndpointName = ReadOnlySettings.EndpointName(); - } - - public void Stop() - { - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScaleOut/When_no_discriminator_is_available.cs b/src/NServiceBus.AcceptanceTests/ScaleOut/When_no_discriminator_is_available.cs deleted file mode 100644 index d07ae69fbb0..00000000000 --- a/src/NServiceBus.AcceptanceTests/ScaleOut/When_no_discriminator_is_available.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace NServiceBus.AcceptanceTests.ScaleOut -{ - using System; - using System.Linq; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NServiceBus.Transports; - using NUnit.Framework; - - public class When_no_discriminator_is_available : NServiceBusAcceptanceTest - { - [Test] - public void Should_blow_up() - { - var ex = Assert.Throws(()=> Scenario.Define() - .WithEndpoint().Done(c =>c.EndpointsStarted) - .AllowExceptions() - .Run()); - - var configEx = ex.InnerExceptions.First() - .InnerException; - - Assert.True(configEx.Message.StartsWith("No endpoint instance discriminator found")); - - } - - public class Context : ScenarioContext - { - } - - public class IndividualizedEndpoint : EndpointConfigurationBuilder - { - - public IndividualizedEndpoint() - { - EndpointSetup(c => - { - c.ScaleOut().UniqueQueuePerEndpointInstance(); - c.UseTransport(); - }); - } - } - - public class TransportThatDoesntSetADefaultDiscriminator:TransportDefinition - { - protected override void Configure(BusConfiguration config) - { - config.EnableFeature(); - } - } - - public class TransportThatDoesntSetADefaultDiscriminatorConfigurator : ConfigureTransport - { - protected override void Configure(FeatureConfigurationContext context, string connectionString) - { - - } - - protected override string ExampleConnectionStringForErrorMessage - { - get { return ""; } - } - } - } - - -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllOutboxCapableStorages.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllOutboxCapableStorages.cs index 222b7a50e36..d005887d606 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllOutboxCapableStorages.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllOutboxCapableStorages.cs @@ -4,18 +4,17 @@ using AcceptanceTesting.Support; using NServiceBus.Persistence; - public class AllOutboxCapableStorages:ScenarioDescriptor + public class AllOutboxCapableStorages : ScenarioDescriptor { public AllOutboxCapableStorages() { - var defaultStorage = Persistence.Default; + var defaultSettings = Persistence.Default; - var definitionType = Type.GetType(defaultStorage.Settings["Persistence"]); - - var definition = (PersistenceDefinition)Activator.CreateInstance(definitionType, true); + var definitionType = defaultSettings.Settings.Get("Persistence"); + var definition = (PersistenceDefinition) Activator.CreateInstance(definitionType, true); if (definition.HasSupportFor()) { - Add(defaultStorage); + Add(defaultSettings); } } } diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransactionSettings.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransactionSettings.cs deleted file mode 100644 index f7f1553e84c..00000000000 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransactionSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus.AcceptanceTests.ScenarioDescriptors -{ - using NServiceBus.AcceptanceTesting.Support; - - public class AllTransactionSettings : ScenarioDescriptor - { - public AllTransactionSettings() - { - Add(TransactionSettings.DistributedTransaction); - Add(TransactionSettings.LocalTransaction); - Add(TransactionSettings.NoTransaction); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransports.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransports.cs index 53b1daebf93..b5dd003e890 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransports.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/AllTransports.cs @@ -5,50 +5,34 @@ using System.Linq; using System.Reflection; using AcceptanceTesting.Support; - using Hosting.Helpers; - using NServiceBus.Transports; + using NServiceBus.Hosting.Helpers; public class AllTransports : ScenarioDescriptor { - public AllTransports() + protected AllTransports() { AddRange(ActiveTransports); } - static IEnumerable ActiveTransports + static IEnumerable ActiveTransports => new List { - get - { - if (activeTransports == null) - { - //temporary fix until we can get rid of the "AllTransports" all together - activeTransports = new List - { - Transports.Default - }; - } - - return activeTransports; - } - } - - static ICollection activeTransports; + Transports.Default + }; } public class AllDtcTransports : AllTransports { public AllDtcTransports() { - AllTransportsFilter.Run(t => t.HasSupportForDistributedTransactions.HasValue - && !t.HasSupportForDistributedTransactions.Value, Remove); + ScenarioFilter.Run(this, Remove); } } - public class AllBrokerTransports : AllTransports + public class AllNativeMultiQueueTransactionTransports : AllTransports { - public AllBrokerTransports() + public AllNativeMultiQueueTransactionTransports() { - AllTransportsFilter.Run(t => !t.HasNativePubSubSupport, Remove); + ScenarioFilter.Run(this, Remove); } } @@ -56,7 +40,7 @@ public class AllTransportsWithCentralizedPubSubSupport : AllTransports { public AllTransportsWithCentralizedPubSubSupport() { - AllTransportsFilter.Run(t => !t.HasSupportForCentralizedPubSub, Remove); + ScenarioFilter.Run(this, Remove); } } @@ -64,31 +48,28 @@ public class AllTransportsWithMessageDrivenPubSub : AllTransports { public AllTransportsWithMessageDrivenPubSub() { - AllTransportsFilter.Run(t => t.HasNativePubSubSupport, Remove); + ScenarioFilter.Run(this, Remove); } } - public class MsmqOnly : ScenarioDescriptor + public class AllTransportsWithoutNativeDeferral : AllTransports { - public MsmqOnly() + public AllTransportsWithoutNativeDeferral() { - if (Transports.Default == Transports.Msmq) - { - Add(Transports.Msmq); - } + ScenarioFilter.Run(this, Remove); } } - public class TypeScanner + public class AllTransportsWithoutNativeDeferralAndWithAtomicSendAndReceive : AllTransports { - - public static IEnumerable GetAllTypesAssignableTo() + public AllTransportsWithoutNativeDeferralAndWithAtomicSendAndReceive() { - return AvailableAssemblies.SelectMany(a => a.GetTypes()) - .Where(t => typeof (T).IsAssignableFrom(t) && t != typeof(T)) - .ToList(); + ScenarioFilter.Run(this, Remove); } + } + public class TypeScanner + { static IEnumerable AvailableAssemblies { get @@ -97,28 +78,54 @@ static IEnumerable AvailableAssemblies { var result = new AssemblyScanner().GetScannableAssemblies(); - assemblies = result.Assemblies; + assemblies = result.Assemblies.Where(a => + { + var references = a.GetReferencedAssemblies(); + + return references.All(an => an.Name != "nunit.framework"); + }).ToList(); } - + return assemblies; } } + public static IEnumerable GetAllTypesAssignableTo() + { + return AvailableAssemblies.SelectMany(a => a.GetTypes()) + .Where(t => typeof(T).IsAssignableFrom(t) && t != typeof(T)) + .ToList(); + } + static List assemblies; } - public static class AllTransportsFilter + public static class ScenarioFilter { - public static void Run(Func condition, Func remove) + public static void Run(ScenarioDescriptor scenarioDescriptor, Func remove) { - foreach (var rundescriptor in Transports.AllAvailable) + var runDescriptors = Transports.AllAvailable; + foreach (var rundescriptor in runDescriptors) { - var transportAssemblyQualifiedName = rundescriptor.Settings["Transport"]; - var type = Type.GetType(transportAssemblyQualifiedName); - if (type != null) + Type type; + if (rundescriptor.Settings.TryGet("Transport", out type)) { - var transport = Activator.CreateInstance(type, true) as TransportDefinition; - if (condition(transport)) + var configurerTypeName = "ConfigureScenariosFor" + type.Name; + var configurerType = Type.GetType(configurerTypeName, false); + + if (configurerType == null) + { + throw new InvalidOperationException($"Acceptance Test project must include a non-namespaced class named '{configurerTypeName}' implementing {typeof(IConfigureSupportedScenariosForTestExecution).Name}. See {typeof(ConfigureScenariosForMsmqTransport).FullName} for an example."); + } + + var configurer = Activator.CreateInstance(configurerType) as IConfigureSupportedScenariosForTestExecution; + + if (configurer == null) + { + throw new InvalidOperationException($"{configurerTypeName} does not implement {typeof(IConfigureSupportedScenariosForTestExecution).Name}."); + } + + if (configurer.UnsupportedScenarioDescriptorTypes.Contains(scenarioDescriptor.GetType())) { remove(rundescriptor); } diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Builders.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Builders.cs index 652dde2bfc3..95f898a11af 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Builders.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Builders.cs @@ -1,5 +1,6 @@ namespace NServiceBus.AcceptanceTests.ScenarioDescriptors { + using System; using System.Collections.Generic; using System.Linq; using AcceptanceTesting.Support; @@ -7,26 +8,23 @@ public static class Builders { - static IEnumerable GetAllAvailable() - { - var builders = TypeScanner.GetAllTypesAssignableTo() - .Where(t => !t.Assembly.FullName.StartsWith("NServiceBus.Core"))//exclude the default builder - .ToList(); - - return from builder in builders - select (new RunDescriptor - { - Key = builder.Name, - Settings = new Dictionary { { "Builder", builder.AssemblyQualifiedName } } - }); - } + public static RunDescriptor Default => GetAllAvailable().FirstOrDefault(); - public static RunDescriptor Default + static IEnumerable GetAllAvailable() { - get + foreach (var builder in foundDefinitions.Value) { - return GetAllAvailable().FirstOrDefault(); + var descriptor = new RunDescriptor(builder.Name); + descriptor.Settings.Set("Builder", builder); + yield return descriptor; } } + + static Lazy> foundDefinitions = new Lazy>(() => + { + return TypeScanner.GetAllTypesAssignableTo() + .Where(t => !t.Assembly.FullName.StartsWith("NServiceBus.Core")) //exclude the default builder + .ToList(); + }); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/EnvironmentHelper.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/EnvironmentHelper.cs new file mode 100644 index 00000000000..b55bc4f4c68 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/EnvironmentHelper.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.AcceptanceTests.ScenarioDescriptors +{ + using System; + + class EnvironmentHelper + { + public static string GetEnvironmentVariable(string variable) + { + var candidate = Environment.GetEnvironmentVariable(variable, EnvironmentVariableTarget.User); + + if (string.IsNullOrWhiteSpace(candidate)) + { + return Environment.GetEnvironmentVariable(variable); + } + + return candidate; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Persistence.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Persistence.cs index 4eef9fbce4e..c3c86aa9723 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Persistence.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Persistence.cs @@ -12,21 +12,24 @@ public static RunDescriptor Default { get { - var specificPersistence = Environment.GetEnvironmentVariable("Persistence.UseSpecific"); + var specificPersistence = EnvironmentHelper.GetEnvironmentVariable("Persistence.UseSpecific"); + var runDescriptors = AllAvailable; if (!string.IsNullOrEmpty(specificPersistence)) { - return AllAvailable.Single(r => r.Key == specificPersistence); + return runDescriptors.Single(r => r.Key == specificPersistence); } - var nonCorePersister = AllAvailable.FirstOrDefault(); + var nonCorePersister = runDescriptors.FirstOrDefault(); if (nonCorePersister != null) { return nonCorePersister; } - return InMemoryPersistenceDescriptor; + var inMemory = new RunDescriptor(InMemoryPersistenceType.Name); + inMemory.Settings.Set("Persistence", InMemoryPersistenceType); + return inMemory; } } @@ -34,52 +37,33 @@ static IEnumerable AllAvailable { get { - if (availablePersisters == null) + foreach (var definition in foundDefinitions.Value) { - availablePersisters = GetAllAvailable().ToList(); - } - - return availablePersisters; - } - } - - static Type InMemoryPersistenceType = typeof(InMemoryPersistence); - - static RunDescriptor InMemoryPersistenceDescriptor = new RunDescriptor - { - Key = InMemoryPersistenceType.Name, - Settings = - new Dictionary - { - {"Persistence", InMemoryPersistenceType.AssemblyQualifiedName} - } - }; + var key = definition.Name; - static IEnumerable GetAllAvailable() - { - var foundDefinitions = TypeScanner.GetAllTypesAssignableTo() - .Where(t => t.Assembly != InMemoryPersistenceType.Assembly && - t.Assembly != typeof(Persistence).Assembly); + var runDescriptor = new RunDescriptor(key); + runDescriptor.Settings.Set("Persistence", definition); - foreach (var definition in foundDefinitions) - { - var key = definition.Name; + var connectionString = EnvironmentHelper.GetEnvironmentVariable(key + ".ConnectionString"); - var runDescriptor = new RunDescriptor - { - Key = key, - Settings = - new Dictionary - { - {"Persistence", definition.AssemblyQualifiedName} - } - }; + if (!string.IsNullOrEmpty(connectionString)) + { + runDescriptor.Settings.Set("Persistence.ConnectionString", connectionString); + } - yield return runDescriptor; + yield return runDescriptor; + } } } - static IList availablePersisters; + static Type InMemoryPersistenceType = typeof(InMemoryPersistence); + static Lazy> foundDefinitions = new Lazy>(() => + { + return TypeScanner.GetAllTypesAssignableTo() + .Where(t => t.Assembly != InMemoryPersistenceType.Assembly && + t.Assembly != typeof(Persistence).Assembly) + .ToList(); + }); } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Serializers.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Serializers.cs index f0bf16b5a59..882d727dfce 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Serializers.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Serializers.cs @@ -1,56 +1,27 @@ namespace NServiceBus.AcceptanceTests.ScenarioDescriptors { - using System.Collections.Generic; using AcceptanceTesting.Support; public static class Serializers { - public static readonly RunDescriptor Binary = new RunDescriptor + public static RunDescriptor Xml + { + get { - Key = "Binary", - Settings = - new Dictionary - { - { - "Serializer", typeof (BinarySerializer).AssemblyQualifiedName - } - } - }; + var xml = new RunDescriptor("Xml"); + xml.Settings.Set("Serializer", typeof(XmlSerializer)); + return xml; + } + } - public static readonly RunDescriptor Bson = new RunDescriptor + public static RunDescriptor Json + { + get { - Key = "Bson", - Settings = - new Dictionary - { - { - "Serializer", typeof (BsonSerializer).AssemblyQualifiedName - } - } - }; - - public static readonly RunDescriptor Xml = new RunDescriptor - { - Key = "Xml", - Settings = - new Dictionary - { - { - "Serializer", typeof (XmlSerializer).AssemblyQualifiedName - } - } - }; - - public static readonly RunDescriptor Json = new RunDescriptor - { - Key = "Json", - Settings = - new Dictionary - { - { - "Serializer", typeof (JsonSerializer).AssemblyQualifiedName - } - } - }; + var json = new RunDescriptor("Json"); + json.Settings.Set("Serializer", typeof(JsonSerializer)); + return json; + } + } } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/TransactionSettings.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/TransactionSettings.cs deleted file mode 100644 index 9ba576f0681..00000000000 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/TransactionSettings.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; - -namespace NServiceBus.AcceptanceTests.ScenarioDescriptors -{ - using NServiceBus.AcceptanceTesting.Support; - - - public static class TransactionSettings - { - public static readonly RunDescriptor DistributedTransaction = new RunDescriptor - { - Key = "DistributedTransaction", - Settings = - new Dictionary() - }; - - public static readonly RunDescriptor LocalTransaction = new RunDescriptor - { - Key = "LocalTransaction", - Settings = - new Dictionary - { - {"Transactions.SuppressDistributedTransactions", bool.TrueString} - } - }; - - public static readonly RunDescriptor NoTransaction = new RunDescriptor - { - Key = "NoTransaction", - Settings = - new Dictionary - { - {"Transactions.Disable", bool.TrueString}, - } - }; - } -} diff --git a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Transports.cs b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Transports.cs index 5040676e0ea..9b8d83fcfac 100644 --- a/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Transports.cs +++ b/src/NServiceBus.AcceptanceTests/ScenarioDescriptors/Transports.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; using AcceptanceTesting.Support; - using NServiceBus.Transports; + using Transport; public static class Transports { @@ -12,85 +12,69 @@ internal static IEnumerable AllAvailable { get { - lock (lockObject) + foreach (var transportDefinitionType in foundDefinitions.Value) { - if (availableTransports == null) + var key = transportDefinitionType.Name; + + var runDescriptor = new RunDescriptor(key); + runDescriptor.Settings.Set("Transport", transportDefinitionType); + + var connectionString = EnvironmentHelper.GetEnvironmentVariable(key + ".ConnectionString"); + + if (string.IsNullOrEmpty(connectionString) && DefaultConnectionStrings.ContainsKey(key)) { - availableTransports = GetAllAvailable().ToList(); + connectionString = DefaultConnectionStrings[key]; } - } - return availableTransports; + if (!string.IsNullOrEmpty(connectionString)) + { + runDescriptor.Settings.Set("Transport.ConnectionString", connectionString); + yield return runDescriptor; + } + else + { + var transportDefinition = (TransportDefinition) Activator.CreateInstance(transportDefinitionType); + + if (!transportDefinition.RequiresConnectionString) + { + yield return runDescriptor; + } + } + } } } - public static RunDescriptor Default { get { - var specificTransport = Environment.GetEnvironmentVariable("Transport.UseSpecific"); + var specificTransport = EnvironmentHelper.GetEnvironmentVariable("Transport.UseSpecific"); + var runDescriptors = AllAvailable.ToList(); if (!string.IsNullOrEmpty(specificTransport)) - return AllAvailable.Single(r => r.Key == specificTransport); - - var transportsOtherThanMsmq = AllAvailable.Where(t => t != Msmq); - - if (transportsOtherThanMsmq.Count() == 1) - return transportsOtherThanMsmq.First(); - - return Msmq; - } - } - - public static RunDescriptor Msmq - { - get { return AllAvailable.SingleOrDefault(r => r.Key == "MsmqTransport"); } - } - - static IEnumerable GetAllAvailable() - { - var foundTransportDefinitions = TypeScanner.GetAllTypesAssignableTo(); - - - foreach (var transportDefinitionType in foundTransportDefinitions) - { - var key = transportDefinitionType.Name; - - var runDescriptor = new RunDescriptor { - Key = key, - Settings = - new Dictionary - { - {"Transport", transportDefinitionType.AssemblyQualifiedName} - } - }; - - var connectionString = Environment.GetEnvironmentVariable(key + ".ConnectionString"); - - if (string.IsNullOrEmpty(connectionString) && DefaultConnectionStrings.ContainsKey(key)) - connectionString = DefaultConnectionStrings[key]; + return runDescriptors.Single(r => r.Key == specificTransport); + } + var transportsOtherThanMsmq = runDescriptors.Where(t => t.Key != MsmqDescriptorKey).ToList(); - if (!string.IsNullOrEmpty(connectionString)) + if (transportsOtherThanMsmq.Count == 1) { - runDescriptor.Settings.Add("Transport.ConnectionString", connectionString); - yield return runDescriptor; + return transportsOtherThanMsmq.First(); } + + return runDescriptors.Single(t => t.Key == MsmqDescriptorKey); } } - static IList availableTransports; - static readonly object lockObject = new object(); - - static readonly Dictionary DefaultConnectionStrings = new Dictionary - { - {"RabbitMQTransport", "host=localhost"}, - {"SqlServerTransport", @"Server=localhost\sqlexpress;Database=nservicebus;Trusted_Connection=True;"}, - {"MsmqTransport", @"cacheSendConnection=false;journal=false;"} - }; - + static string MsmqDescriptorKey = "MsmqTransport"; + static Lazy> foundDefinitions = new Lazy>(() => TypeScanner.GetAllTypesAssignableTo().ToList()); + static Dictionary DefaultConnectionStrings = new Dictionary + { + {"RabbitMQTransport", "host=localhost"}, + {"SqlServerTransport", @"Server=localhost\sqlexpress;Database=nservicebus;Trusted_Connection=True;"}, + {"MsmqTransport", "cacheSendConnection=false;journal=false;"} + }; } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Scheduling/When_scheduling_a_recurring_task.cs b/src/NServiceBus.AcceptanceTests/Scheduling/When_scheduling_a_recurring_task.cs index ec67eafbb63..0108646bf62 100644 --- a/src/NServiceBus.AcceptanceTests/Scheduling/When_scheduling_a_recurring_task.cs +++ b/src/NServiceBus.AcceptanceTests/Scheduling/When_scheduling_a_recurring_task.cs @@ -1,61 +1,80 @@ namespace NServiceBus.AcceptanceTests.Scheduling { using System; - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; + using EndpointTemplates; + using Features; using NUnit.Framework; using ScenarioDescriptors; public class When_scheduling_a_recurring_task : NServiceBusAcceptanceTest { [Test] - public void Should_execute_the_task() + public Task Should_execute_the_task() { - Scenario.Define() - .WithEndpoint() - .Done(c => c.InvokedAt.HasValue) - .Repeat(r => r.For(Transports.Default)) - .Should(c => - { - Assert.True(c.InvokedAt.HasValue); - Assert.Greater(c.InvokedAt.Value - c.RequestedAt, TimeSpan.FromSeconds(5)); - }) - .Run(TimeSpan.FromSeconds(20)); + return Scenario.Define() + .WithEndpoint() + .Done(c => c.InvokedAt.HasValue) + .Repeat(r => r.For(Transports.Default)) + .Should(c => + { + Assert.True(c.InvokedAt.HasValue); + Assert.Greater(c.InvokedAt.Value - c.RequestedAt, TimeSpan.FromMilliseconds(5)); + }) + .Run(TimeSpan.FromSeconds(60)); } - public class Context : ScenarioContext + class Context : ScenarioContext { - public DateTime? InvokedAt{ get; set; } - public DateTime RequestedAt{ get; set; } + public DateTime? InvokedAt { get; set; } + public DateTime RequestedAt { get; set; } } - public class SchedulingEndpoint : EndpointConfigurationBuilder + class SetupScheduledAction : Feature { - public SchedulingEndpoint() + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new SetupScheduledActionTask(b.Build())); + } + } + + class SetupScheduledActionTask : FeatureStartupTask + { + public SetupScheduledActionTask(Context context) { - EndpointSetup(); + this.context = context; } - class SetupScheduledAction : IWantToRunWhenBusStartsAndStops + protected override Task OnStart(IMessageSession session) { - public Schedule Schedule { get; set; } - public Context Context { get; set; } - public void Start() + context.RequestedAt = DateTime.UtcNow; + + return session.ScheduleEvery(TimeSpan.FromMilliseconds(5), "MyTask", c => { - Context.RequestedAt = DateTime.UtcNow; + context.InvokedAt = DateTime.UtcNow; + return Task.FromResult(0); + }); + } - Schedule.Every(TimeSpan.FromSeconds(5), "MyTask", () => - { - Context.InvokedAt = DateTime.UtcNow; - }); - } + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + + Context context; + } - public void Stop() + class SchedulingEndpoint : EndpointConfigurationBuilder + { + public SchedulingEndpoint() + { + EndpointSetup(config => { - } + config.EnableFeature(); + config.EnableFeature(); + }); } } } - - } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/SelfVerification/When_running_saga_tests.cs b/src/NServiceBus.AcceptanceTests/SelfVerification/When_running_saga_tests.cs new file mode 100644 index 00000000000..b320b1862e0 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/SelfVerification/When_running_saga_tests.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.AcceptanceTests.SelfVerification +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using NUnit.Framework; + + [TestFixture] + public class When_running_saga_tests : NServiceBusAcceptanceTest + { + [Test] + public void All_saga_entities_in_acceptance_tests_should_have_virtual_properties() + { + // Because otherwise NHibernate gets cranky! + var sagaEntities = Assembly.GetExecutingAssembly().GetTypes() + .Where(t => typeof(IContainSagaData).IsAssignableFrom(t) && !t.IsInterface && + //only include core tests + t.Namespace != null && t.Namespace.StartsWith("NServiceBus.AcceptanceTests")) + .ToArray(); + + var offenders = 0; + + foreach (var entity in sagaEntities) + { + foreach (var property in entity.GetProperties()) + { + if (property.GetGetMethod().IsVirtual) + { + Console.WriteLine("OK: {0}.{1}", entity.FullName, property.Name); + } + else + { + offenders++; + Console.WriteLine("ERROR: {0}.{1} must be marked as virtual for NHibernate tests to succeed.", entity.FullName, property.Name); + } + } + } + + Assert.AreEqual(0, offenders); + } + + [Test] + public void All_sagas_and_entities_should_have_unique_names() + { + var allTypes = Assembly.GetExecutingAssembly().GetTypes(); + + var sagas = allTypes.Where(t => typeof(Saga).IsAssignableFrom(t)).ToArray(); + var sagaEntities = allTypes.Where(t => typeof(IContainSagaData).IsAssignableFrom(t) && !t.IsInterface) + .ToArray(); + + var nestedSagaEntityParents = sagaEntities + .Where(t => t.DeclaringType != null) + .Select(t => t.DeclaringType) + .ToArray(); + + var usedNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); + var offenders = 0; + + Console.WriteLine("Sagas / Saga Entities with non-unique names:"); + foreach (var cls in sagas.Union(sagaEntities).Union(nestedSagaEntityParents)) + { + if (usedNames.Contains(cls.Name)) + { + offenders++; + Console.WriteLine(cls.FullName); + } + usedNames.Add(cls.Name); + } + + Assert.AreEqual(0, offenders); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_configuring_custom_xml_namespace.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_configuring_custom_xml_namespace.cs new file mode 100644 index 00000000000..459314f744d --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_configuring_custom_xml_namespace.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System.Text; + using System.Threading.Tasks; + using System.Xml.Linq; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_configuring_custom_xml_namespace : NServiceBusAcceptanceTest + { + const string CustomXmlNamespace = "https://particular.net"; + + [Test] + public async Task Should_use_as_root_namespace_in_messages() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(session => session.SendLocal(new SimpleMessage()))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.AreEqual($"{CustomXmlNamespace}/{typeof(SimpleMessage).Namespace}", context.MessageNamespace); + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public string MessageNamespace { get; set; } + } + + class EndpointUsingCustomNamespace : EndpointConfigurationBuilder + { + public EndpointUsingCustomNamespace() + { + EndpointSetup(c => + { + c.UseSerialization().Namespace(CustomXmlNamespace); + c.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + class SimpleMessageHandler : IHandleMessages + { + public SimpleMessageHandler(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task Handle(SimpleMessage message, IMessageHandlerContext context) + { + scenarioContext.MessageReceived = true; + return Task.FromResult(0); + } + + Context scenarioContext; + } + + public class IncomingMutator : IMutateIncomingTransportMessages + { + public IncomingMutator(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + var document = XDocument.Parse(Encoding.UTF8.GetString(context.Body)); + var defaultNamespace = document.Root?.GetDefaultNamespace(); + scenarioContext.MessageNamespace = defaultNamespace?.NamespaceName; + return Task.FromResult(0); + } + + Context scenarioContext; + } + } + + class SimpleMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_registering_additional_deserializers.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_additional_deserializers.cs new file mode 100644 index 00000000000..a2912773b80 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_additional_deserializers.cs @@ -0,0 +1,140 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.Serialization.Formatters.Binary; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using MessageInterfaces; + using NServiceBus.Serialization; + using NUnit.Framework; + using Settings; + + public class When_registering_additional_deserializers : NServiceBusAcceptanceTest + { + [Test] + public async Task Two_endpoints_with_different_serializers_should_deserialize_the_message() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When( + (session, c) => + { + var sendOptions = new SendOptions(); + sendOptions.SetHeader("ContentType", "MyCustomSerializer"); + return session.Send(new MyRequest()); + })) + .WithEndpoint() + .Done(c => c.HandlerGotTheRequest) + .Run(); + + Assert.True(context.HandlerGotTheRequest); + Assert.True(context.SerializeCalled); + Assert.True(context.DeserializeCalled); + Assert.AreEqual("SomeFancySettings", context.ValueFromSettings); + } + + public class Context : ScenarioContext + { + public bool HandlerGotTheRequest { get; set; } + public bool SerializeCalled { get; set; } + public bool DeserializeCalled { get; set; } + public string ValueFromSettings { get; set; } + } + + class CustomSerializationSender : EndpointConfigurationBuilder + { + public CustomSerializationSender() + { + EndpointSetup(c => c.UseSerialization().Settings((Context) ScenarioContext, "")) + .AddMapping(typeof(XmlCustomSerializationReceiver)); + } + } + + class XmlCustomSerializationReceiver : EndpointConfigurationBuilder + { + public XmlCustomSerializationReceiver() + { + EndpointSetup(c => + { + c.UseSerialization(); + c.AddDeserializer().Settings((Context) ScenarioContext, "SomeFancySettings"); + }); + } + + class MyRequestHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyRequest request, IMessageHandlerContext context) + { + Context.HandlerGotTheRequest = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + class MyRequest : IMessage + { + } + + public class MyCustomSerializer : SerializationDefinition + { + public override Func Configure(ReadOnlySettings settings) + { + var context = settings.Get(); + return mapper => new MyCustomMessageSerializer(context, settings.Get("MyCustomSerializer.Settings.Value")); + } + } + + class MyCustomMessageSerializer : IMessageSerializer + { + public MyCustomMessageSerializer(Context context, string valueFromSettings) + { + this.context = context; + this.valueFromSettings = valueFromSettings; + } + + public void Serialize(object message, Stream stream) + { + var serializer = new BinaryFormatter(); + + context.SerializeCalled = true; + + serializer.Serialize(stream, message); + } + + public object[] Deserialize(Stream stream, IList messageTypes = null) + { + var serializer = new BinaryFormatter(); + + stream.Position = 0; + var msg = serializer.Deserialize(stream); + context.DeserializeCalled = true; + context.ValueFromSettings = valueFromSettings; + + return new[] + { + msg + }; + } + + public string ContentType => "MyCustomSerializer"; + readonly Context context; + readonly string valueFromSettings; + } + } + + static class CustomSettingsForMyCustomSerializer + { + public static void Settings(this SerializationExtensions extensions, When_registering_additional_deserializers.Context context, string valueFromSettings) + { + var settings = extensions.GetSettings(); + settings.Set("MyCustomSerializer.Settings", context); + settings.Set("MyCustomSerializer.Settings.Value", valueFromSettings); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_registering_custom_serializer.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_custom_serializer.cs new file mode 100644 index 00000000000..511ecf91a67 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_custom_serializer.cs @@ -0,0 +1,109 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.Serialization.Formatters.Binary; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using MessageInterfaces; + using NServiceBus.Serialization; + using NUnit.Framework; + using Settings; + + public class When_registering_custom_serializer : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_the_custom_serializer() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When( + (session, c) => session.SendLocal(new MyRequest()))) + .Done(c => c.HandlerGotTheRequest) + .Run(); + + Assert.IsTrue(context.SerializeCalled); + Assert.IsTrue(context.DeserializeCalled); + } + + public class Context : ScenarioContext + { + public bool HandlerGotTheRequest { get; set; } + public bool SerializeCalled { get; set; } + public bool DeserializeCalled { get; set; } + } + + public class EndpointWithCustomSerializer : EndpointConfigurationBuilder + { + public EndpointWithCustomSerializer() + { + var context = (Context) ScenarioContext; + EndpointSetup(c => + { + c.UseSerialization(); + c.GetSettings().Set(context); + }); + } + + public class MyRequestHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyRequest request, IMessageHandlerContext context) + { + Context.HandlerGotTheRequest = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyRequest : IMessage + { + } + + class MySuperSerializer : SerializationDefinition + { + public override Func Configure(ReadOnlySettings settings) + { + return mapper => new MyCustomSerializer(settings.Get()); + } + } + + class MyCustomSerializer : IMessageSerializer + { + public MyCustomSerializer(Context context) + { + this.context = context; + } + + public void Serialize(object message, Stream stream) + { + context.SerializeCalled = true; + + var serializer = new BinaryFormatter(); + serializer.Serialize(stream, message); + } + + public object[] Deserialize(Stream stream, IList messageTypes = null) + { + var serializer = new BinaryFormatter(); + + stream.Position = 0; + var msg = serializer.Deserialize(stream); + + context.DeserializeCalled = true; + + return new[] + { + msg + }; + } + + public string ContentType => "MyCustomSerializer"; + readonly Context context; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_registering_deserializers_with_settings.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_deserializers_with_settings.cs new file mode 100644 index 00000000000..b3ee8a973e6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_registering_deserializers_with_settings.cs @@ -0,0 +1,140 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.Serialization.Formatters.Binary; + using System.Threading.Tasks; + using AcceptanceTesting; + using Configuration.AdvanceExtensibility; + using EndpointTemplates; + using MessageInterfaces; + using NServiceBus.Serialization; + using NUnit.Framework; + using Settings; + + public class When_registering_deserializers_with_settings : NServiceBusAcceptanceTest + { + const string Value1 = "SomeFancySettingsForMainSerializer"; + const string Value2 = "SomeFancySettingsForDeserializer"; + + [Test] + public async Task Should_not_override_serializer_settings() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When( + (session, c) => + { + var sendOptions = new SendOptions(); + sendOptions.SetHeader("ContentType", "MyCustomSerializer"); + return session.SendLocal(new MyRequest()); + })) + .WithEndpoint() + .Done(c => c.DeserializeCalled) + .Run(); + + Assert.True(context.HandlerGotTheRequest); + Assert.True(context.SerializeCalled); + Assert.True(context.DeserializeCalled); + Assert.AreEqual(Value1, context.ValueFromSettingsForMainSerializer); + Assert.AreEqual(Value2, context.ValueFromSettingsForDeserializer); + } + + public class Context : ScenarioContext + { + public bool HandlerGotTheRequest { get; set; } + public bool SerializeCalled { get; set; } + public bool DeserializeCalled { get; set; } + public string ValueFromSettingsForMainSerializer { get; set; } + public string ValueFromSettingsForDeserializer { get; set; } + } + + class XmlCustomSerializationReceiver : EndpointConfigurationBuilder + { + public XmlCustomSerializationReceiver() + { + var context = (Context)ScenarioContext; + EndpointSetup(c => + { + c.UseSerialization().Settings(Value1, context); + c.AddDeserializer().Settings(Value2, context); + }); + } + + class MyRequestHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyRequest request, IMessageHandlerContext context) + { + Context.HandlerGotTheRequest = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + class MyRequest : IMessage + { + } + + + public class MyCustomSerializer : SerializationDefinition + { + public override Func Configure(ReadOnlySettings settings) + { + return mapper => new MyCustomMessageSerializer(settings.GetOrDefault("MyCustomSerializer.Settings"), settings.Get()); + } + } + + class MyCustomMessageSerializer : IMessageSerializer + { + readonly string valueFromSettings; + readonly Context context; + + public MyCustomMessageSerializer(string valueFromSettings, Context context) + { + this.valueFromSettings = valueFromSettings; + this.context = context; + } + + public void Serialize(object message, Stream stream) + { + var serializer = new BinaryFormatter(); + + context.SerializeCalled = true; + context.ValueFromSettingsForMainSerializer = valueFromSettings; + + serializer.Serialize(stream, message); + } + + public object[] Deserialize(Stream stream, IList messageTypes = null) + { + var serializer = new BinaryFormatter(); + + stream.Position = 0; + var msg = serializer.Deserialize(stream); + context.DeserializeCalled = true; + context.ValueFromSettingsForDeserializer = valueFromSettings; + + return new[] + { + msg + }; + } + + public string ContentType => "MyCustomSerializer"; + } + } + + static class CustomSettingsForMyCustomSerializer2 + { + public static SerializationExtensions Settings(this SerializationExtensions extensions, string valueFromSettings, When_registering_deserializers_with_settings.Context context) + { + var settings = extensions.GetSettings(); + settings.Set("MyCustomSerializer.Settings", valueFromSettings); + settings.Set(context); + return extensions; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_sanitizing_xml_messages.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_sanitizing_xml_messages.cs new file mode 100644 index 00000000000..97c08837ec3 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_sanitizing_xml_messages.cs @@ -0,0 +1,88 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System.Text; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_sanitizing_xml_messages : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_remove_illegal_characters_from_messages() + { + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(session => session.SendLocal(new SimpleMessage + { + Value = "Hello World!" + }))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.That(context.Input, Is.EqualTo("Hello World!")); + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public string Input { get; set; } + } + + class EndpointSanitizingInput : EndpointConfigurationBuilder + { + public EndpointSanitizingInput() + { + EndpointSetup(c => + { + c.UseSerialization().SanitizeInput(); + c.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + class SimpleMessageHandler : IHandleMessages + { + public SimpleMessageHandler(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task Handle(SimpleMessage message, IMessageHandlerContext context) + { + scenarioContext.MessageReceived = true; + scenarioContext.Input = message.Value; + + return Task.FromResult(0); + } + + Context scenarioContext; + } + + public class IncomingMutator : IMutateIncomingTransportMessages + { + public IncomingMutator(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + var body = Encoding.UTF8.GetString(context.Body); + var invalidChar = char.ConvertFromUtf32(0x8); + + context.Body = Encoding.UTF8.GetBytes(body.Replace("Hello World!", $"{invalidChar}Hello {invalidChar}World!{invalidChar}")); + + return Task.FromResult(0); + } + + Context scenarioContext; + } + } + + class SimpleMessage : ICommand + { + public string Value { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_skip_wrapping_xml.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_skip_wrapping_xml.cs new file mode 100644 index 00000000000..c3caaf781fb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_skip_wrapping_xml.cs @@ -0,0 +1,92 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Xml.Linq; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_skip_wrapping_xml : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_wrap_xml_content() + { + XNamespace ns = "demoNamepsace"; + var xmlContent = new XDocument(new XElement(ns + "Document", new XElement(ns + "Value", "content"))); + + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(session => session.SendLocal(new MessageWithRawXml { Document = xmlContent }))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.That(context.XmlPropertyValue.ToString(), Is.EqualTo(xmlContent.ToString())); + Assert.That(context.XmlMessage.Root.Name.LocalName, Is.EqualTo(nameof(MessageWithRawXml))); + Assert.That(context.XmlMessage.Root.Elements().Single().ToString(), Is.EqualTo(xmlContent.ToString())); + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + + public XDocument XmlMessage { get; set; } + + public XDocument XmlPropertyValue { get; set; } + } + + class NonWrappingEndpoint : EndpointConfigurationBuilder + { + public NonWrappingEndpoint() + { + EndpointSetup(c => + { + c.UseSerialization().DontWrapRawXml(); + c.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + class RawXmlMessageHandler : IHandleMessages + { + public RawXmlMessageHandler(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task Handle(MessageWithRawXml messageWithRawXml, IMessageHandlerContext context) + { + scenarioContext.MessageReceived = true; + scenarioContext.XmlPropertyValue = messageWithRawXml.Document; + + return Task.FromResult(0); + } + + Context scenarioContext; + } + + public class IncomingMutator : IMutateIncomingTransportMessages + { + public IncomingMutator(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + scenarioContext.XmlMessage = XDocument.Parse(Encoding.UTF8.GetString(context.Body)); + + return Task.FromResult(0); + } + + Context scenarioContext; + } + } + + class MessageWithRawXml : ICommand + { + public XDocument Document { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_wrapping_is_not_skipped.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_wrapping_is_not_skipped.cs new file mode 100644 index 00000000000..a87d67f8524 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_wrapping_is_not_skipped.cs @@ -0,0 +1,94 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Xml.Linq; + using AcceptanceTesting; + using EndpointTemplates; + using MessageMutator; + using NUnit.Framework; + + public class When_wrapping_is_not_skipped : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_wrap_xml_content() + { + XNamespace ns = "demoNamepsace"; + var xmlContent = new XDocument(new XElement(ns + "Document", new XElement(ns + "Value", "content"))); + + var context = await Scenario.Define() + .WithEndpoint(e => e + .When(session => session.SendLocal(new MessageWithRawXml { Document = xmlContent }))) + .Done(c => c.MessageReceived) + .Run(); + + Assert.That(context.XmlPropertyValue.ToString(), Is.EqualTo(xmlContent.ToString())); + Assert.That(context.XmlMessage.Root.Name.LocalName, Is.EqualTo(nameof(MessageWithRawXml))); + Assert.That(context.XmlMessage.Root.Elements().Single().Name.LocalName, Is.EqualTo("Document")); + Assert.That(context.XmlMessage.Root.Elements().Single().Elements().Single().Name.LocalName, Is.EqualTo("Document")); + Assert.That(context.XmlMessage.Root.Elements().Single().Elements().Single().ToString(), Is.EqualTo(xmlContent.ToString())); + } + + class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + + public XDocument XmlMessage { get; set; } + + public XDocument XmlPropertyValue { get; set; } + } + + class WrappingEndpoint : EndpointConfigurationBuilder + { + public WrappingEndpoint() + { + EndpointSetup(c => + { + c.UseSerialization(); // wrapping is enabled by default + c.RegisterComponents(r => r.ConfigureComponent(DependencyLifecycle.SingleInstance)); + }); + } + + class RawXmlMessageHandler : IHandleMessages + { + public RawXmlMessageHandler(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task Handle(MessageWithRawXml messageWithRawXml, IMessageHandlerContext context) + { + scenarioContext.MessageReceived = true; + scenarioContext.XmlPropertyValue = messageWithRawXml.Document; + + return Task.FromResult(0); + } + + Context scenarioContext; + } + + public class IncomingMutator : IMutateIncomingTransportMessages + { + public IncomingMutator(Context scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + scenarioContext.XmlMessage = XDocument.Parse(Encoding.UTF8.GetString(context.Body)); + + return Task.FromResult(0); + } + + Context scenarioContext; + } + } + + class MessageWithRawXml : ICommand + { + public XDocument Document { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Serialization/When_xml_serializer_used_with_unobtrusive_mode.cs b/src/NServiceBus.AcceptanceTests/Serialization/When_xml_serializer_used_with_unobtrusive_mode.cs new file mode 100644 index 00000000000..d86fb4d9116 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Serialization/When_xml_serializer_used_with_unobtrusive_mode.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.AcceptanceTests.Serialization +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_xml_serializer_used_with_unobtrusive_mode : NServiceBusAcceptanceTest + { + [Test] + public async Task Message_should_be_received_with_deserialized_payload() + { + var expectedData = 1; + + var context = await Scenario.Define() + .WithEndpoint(c => c.When(s => s.Send(new MyCommand { Data = expectedData }))) + .WithEndpoint() + .Done(c => c.WasCalled) + .Run(TimeSpan.FromSeconds(10)); + + Assert.AreEqual(expectedData, context.Data); + } + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public int Data { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName.EndsWith("Command")); + c.UseSerialization(); + }).AddMapping(typeof(Receiver)) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.Conventions().DefiningCommandsAs(t => t.Namespace != null && t.FullName.EndsWith("Command")); + c.UseSerialization(); + }) + .ExcludeType(); // remove that type from assembly scanning to simulate what would happen with true unobtrusive mode + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(ICommand message, IMessageHandlerContext context) + { + Context.Data = ((MyCommand)message).Data; + Context.WasCalled = true; + return Task.FromResult(0); + } + } + } + + public interface ICommand { } + + public class MyCommand : ICommand + { + public int Data { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Timeout/CyclingOutageTimeoutPersister.cs b/src/NServiceBus.AcceptanceTests/Timeout/CyclingOutageTimeoutPersister.cs new file mode 100644 index 00000000000..ee376a59c02 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Timeout/CyclingOutageTimeoutPersister.cs @@ -0,0 +1,113 @@ +namespace NServiceBus.AcceptanceTests.Timeouts +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using Timeout.Core; + + /// + /// This class mocks outages for timeout storage. + /// If SecondsToWait is set to 10, it will throw exceptions for 10 seconds, then be available for 10 seconds, and repeat. + /// + class CyclingOutageTimeoutPersister : IPersistTimeouts, IQueryTimeouts + { + public CyclingOutageTimeoutPersister(int secondsToWait) + { + this.secondsToWait = secondsToWait; + } + + public Task TryRemove(string timeoutId, ContextBag context) + { + ThrowExceptionUntilWaitTimeReached(); + + TimeoutData timeoutData = null; + + if (storage.ContainsKey(timeoutId)) + { + storage.TryRemove(timeoutId, out timeoutData); + } + + return Task.FromResult(timeoutData != null); + } + + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) + { + ThrowExceptionUntilWaitTimeReached(); + return completedTask; + } + + public Task Add(TimeoutData timeout, ContextBag context) + { + ThrowExceptionUntilWaitTimeReached(); + storage.TryAdd(timeout.Id, timeout); + return completedTask; + } + + public Task Peek(string timeoutId, ContextBag context) + { + ThrowExceptionUntilWaitTimeReached(); + if (storage.ContainsKey(timeoutId)) + { + return Task.FromResult(storage[timeoutId]); + } + return Task.FromResult(null); + } + + public Task GetNextChunk(DateTime startSlice) + { + ThrowExceptionUntilWaitTimeReached(); + + var timeoutsDue = new List(); + foreach (var key in storage.Keys) + { + var value = storage[key]; + if (value.Time <= startSlice) + { + var timeout = new TimeoutsChunk.Timeout(key, value.Time); + timeoutsDue.Add(timeout); + } + } + + var chunk = new TimeoutsChunk(timeoutsDue.ToArray(), DateTime.Now.AddSeconds(5)); + + return Task.FromResult(chunk); + } + + void ThrowExceptionUntilWaitTimeReached() + { + if (NextChangeTime <= DateTime.Now) + { + NextChangeTime = DateTime.Now.AddSeconds(secondsToWait); + isAvailable = !isAvailable; + } + + if (!isAvailable) + { + throw new Exception("Persister is temporarily unavailable"); + } + } + + public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) + { + ThrowExceptionUntilWaitTimeReached(); + nextTimeToRunQuery = DateTime.Now.AddSeconds(2); + return Enumerable.Empty>().ToList(); + } + + public Task Add(TimeoutData timeout) + { + ThrowExceptionUntilWaitTimeReached(); + return completedTask; + } + + Task completedTask = Task.FromResult(0); + DateTime NextChangeTime; + int secondsToWait; + ConcurrentDictionary storage = new ConcurrentDictionary(); + + static bool isAvailable; + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Timeout/When_timeout_storage_is_unavailable_temporarily.cs b/src/NServiceBus.AcceptanceTests/Timeout/When_timeout_storage_is_unavailable_temporarily.cs new file mode 100644 index 00000000000..dd41125f9be --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Timeout/When_timeout_storage_is_unavailable_temporarily.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.Timeouts +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using ScenarioDescriptors; + + class When_timeout_storage_is_unavailable_temporarily : NServiceBusAcceptanceTest + { + [Test] + public Task Endpoint_should_start() + { + return Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Repeat(r => r.For()) + .Should(c => Assert.IsTrue(c.EndpointsStarted)) + .Run(); + } + + [Test] + public async Task Endpoint_should_not_shutdown() + { + var stopTime = DateTime.Now.AddSeconds(45); + + var testContext = + await Scenario.Define(c => { c.SecondsToWait = 10; }) + .WithEndpoint(b => + { + b.CustomConfig((busConfig, context) => + { + busConfig.DefineCriticalErrorAction(criticalErrorContext => + { + context.FatalErrorOccurred = true; + return Task.FromResult(true); + }); + }); + }) + .Done(c => c.FatalErrorOccurred || stopTime <= DateTime.Now) + .Run(); + + Assert.IsFalse(testContext.FatalErrorOccurred, "Circuit breaker was triggered too soon."); + } + + public class TimeoutTestContext : ScenarioContext + { + public int SecondsToWait { get; set; } + public bool FatalErrorOccurred { get; set; } + } + + [Serializable] + public class MyMessage : IMessage + { + } + + public class EndpointWithFlakyTimeoutPersister : EndpointConfigurationBuilder + { + public EndpointWithFlakyTimeoutPersister() + { + EndpointSetup(config => { config.EnableFeature(); }); + } + + public TestContext TestContext { get; set; } + + class Initializer : Feature + { + public Initializer() + { + EnableByDefault(); + } + + protected override void Setup(FeatureConfigurationContext context) + { + var testContext = context.Settings.Get(); + context.Container.ConfigureComponent(b => new CyclingOutageTimeoutPersister(testContext.SecondsToWait), DependencyLifecycle.SingleInstance); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/OutdatedTimeoutPersister.cs b/src/NServiceBus.AcceptanceTests/Timeouts/OutdatedTimeoutPersister.cs deleted file mode 100644 index 2f6b38d1811..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/OutdatedTimeoutPersister.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NServiceBus.Timeout.Core; - - class OutdatedTimeoutPersister : IPersistTimeouts - { - public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) - { - nextTimeToRunQuery = DateTime.Now.AddYears(42); - return Enumerable.Empty>().ToList(); - } - - public void Add(TimeoutData timeout) - { - } - - public bool TryRemove(string timeoutId, out TimeoutData timeoutData) - { - timeoutData = null; - return false; - } - - public void RemoveTimeoutBy(Guid sagaId) - { - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/TemporarilyUnavailableTimeoutPersister.cs b/src/NServiceBus.AcceptanceTests/Timeouts/TemporarilyUnavailableTimeoutPersister.cs deleted file mode 100644 index 09eab4f384b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/TemporarilyUnavailableTimeoutPersister.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Timeout.Core; - - class TemporarilyUnavailableTimeoutPersister : IPersistTimeouts - { - public int SecondsToWait { get; set; } - static bool isAvailable = false; - DateTime NextChangeTime; - - public TemporarilyUnavailableTimeoutPersister() - { - NextChangeTime = DateTime.Now.AddSeconds(SecondsToWait); - } - - private void ThrowExceptionUntilWait() - { - if (NextChangeTime <= DateTime.Now) - { - NextChangeTime = DateTime.Now.AddSeconds(SecondsToWait); - isAvailable = !isAvailable; - } - - if (!isAvailable) - { - throw new Exception("Persister is temporarily unavailable"); - } - } - - public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) - { - ThrowExceptionUntilWait(); - nextTimeToRunQuery = DateTime.Now.AddSeconds(2); - return Enumerable.Empty>().ToList(); - } - - public void Add(TimeoutData timeout) - { - ThrowExceptionUntilWait(); - } - - public bool TryRemove(string timeoutId, out TimeoutData timeoutData) - { - ThrowExceptionUntilWait(); - timeoutData = null; - return true; - } - - public void RemoveTimeoutBy(Guid sagaId) - { - ThrowExceptionUntilWait(); - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/TransportWithFakeQueues.cs b/src/NServiceBus.AcceptanceTests/Timeouts/TransportWithFakeQueues.cs deleted file mode 100644 index 0f61edad0f9..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/TransportWithFakeQueues.cs +++ /dev/null @@ -1,47 +0,0 @@ - -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using NServiceBus.Transports; - using NServiceBus.Unicast; - - public class TransportWithFakeQueues : TransportDefinition - { - protected override void Configure(BusConfiguration config) - { - config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); - config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); - config.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); - } - } - - class FakeDequeuer : IDequeueMessages - { - - public void Init(Address address, Unicast.Transport.TransactionSettings transactionSettings, Func tryProcessMessage, Action endProcessMessage) - { - } - - public void Start(int maximumConcurrencyLevel) - { - } - - public void Stop() - { - } - } - - class FakeQueueCreator : ICreateQueues - { - public void CreateQueueIfNecessary(Address address, string account) - { - } - } - - class FakeSender : ISendMessages - { - public void Send(TransportMessage message, SendOptions sendOptions) - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/UpdatedTimeoutPersister.cs b/src/NServiceBus.AcceptanceTests/Timeouts/UpdatedTimeoutPersister.cs deleted file mode 100644 index 1cc0eb8406b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/UpdatedTimeoutPersister.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NServiceBus.Timeout.Core; - - class UpdatedTimeoutPersister : IPersistTimeouts, IPersistTimeoutsV2 - { - public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) - { - nextTimeToRunQuery = DateTime.Now.AddYears(42); - return Enumerable.Empty>().ToList(); - } - - public void Add(TimeoutData timeout) - { - } - - public bool TryRemove(string timeoutId, out TimeoutData timeoutData) - { - timeoutData = null; - return false; - } - - public void RemoveTimeoutBy(Guid sagaId) - { - } - - public TimeoutData Peek(string timeoutId) - { - return null; - } - - public bool TryRemove(string timeoutId) - { - return true; - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_dispatched_timeout_already_removed_from_timeout_storage.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_dispatched_timeout_already_removed_from_timeout_storage.cs deleted file mode 100644 index 5a2a2b72692..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_dispatched_timeout_already_removed_from_timeout_storage.cs +++ /dev/null @@ -1,187 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using System.Collections.Generic; - using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NServiceBus.ObjectBuilder; - using NServiceBus.Timeout.Core; - using NUnit.Framework; - - public class When_timeout_already_removed : NServiceBusAcceptanceTest - { - [Test] - public void Should_rollback_and_not_deliver_timeout_when_using_dtc() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b - .CustomConfig(configure => configure.Transactions().EnableDistributedTransactions()) - .Given(bus => - { - bus.Defer(TimeSpan.FromSeconds(5), new MyMessage()); - })) - .Done(c => c.AttemptedToRemoveTimeout || c.MessageReceived) - .Run(); - - Assert.IsFalse(context.MessageReceived, "Message should not be delivered using dtc."); - Assert.AreEqual(2, context.NumberOfProcessingAttempts, "The rollback should cause a retry."); - Assert.IsTrue(context.AttemptedToRemoveTimeout); - } - - [Test] - public void Should_rollback_and_deliver_timeout_anyway_when_using_native_tx() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b - .CustomConfig(configure => configure.Transactions().DisableDistributedTransactions()) - .Given(bus => - { - bus.Defer(TimeSpan.FromSeconds(5), new MyMessage()); - })) - .Done(c => c.AttemptedToRemoveTimeout && c.MessageReceived) - .Run(); - - Assert.IsTrue(context.MessageReceived, "Message should be delivered although transaction was aborted."); - Assert.AreEqual(2, context.NumberOfProcessingAttempts, "The rollback should cause a retry."); - Assert.IsTrue(context.AttemptedToRemoveTimeout); - } - - [Test] - public void Should_deliver_timeout_anyway_when_using_no_tx() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(b => b - .CustomConfig(configure => configure.Transactions().Disable()) - .Given(bus => - { - bus.Defer(TimeSpan.FromSeconds(5), new MyMessage()); - })) - .Done(c => c.AttemptedToRemoveTimeout && c.MessageReceived) - .Run(); - - Assert.IsTrue(context.MessageReceived, "Message should be delivered although timeout processing fails."); - Assert.AreEqual(1, context.NumberOfProcessingAttempts, "Should not retry without transactions enabled."); - Assert.IsTrue(context.AttemptedToRemoveTimeout); - } - - public class Context : ScenarioContext - { - public bool MessageReceived { get; set; } - public bool AttemptedToRemoveTimeout { get; set; } - public int NumberOfProcessingAttempts { get; set; } - } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(); - } - - public class DelayedMessageHandler : IHandleMessages - { - Context context; - - public DelayedMessageHandler(Context context) - { - this.context = context; - } - - public void Handle(MyMessage message) - { - context.MessageReceived = true; - } - } - - public class EndpointConfiguration : IWantToRunBeforeConfigurationIsFinalized - { - public static IBuilder builder; - - public void Run(Configure config) - { - builder = config.Builder; - } - } - - public class DispatcherInterceptor : Feature - { - public DispatcherInterceptor() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - var originalPersister = EndpointConfiguration.builder.Build(); - var ctx = EndpointConfiguration.builder.Build(); - context.Container.ConfigureComponent(() => new TimeoutPersistanceWrapper(originalPersister, originalPersister as IPersistTimeoutsV2, ctx), DependencyLifecycle.SingleInstance); - } - } - - class TimeoutPersistanceWrapper : IPersistTimeouts, IPersistTimeoutsV2 - { - IPersistTimeouts originalTimeoutPersister; - IPersistTimeoutsV2 originalTimeoutPersisterV2; - Context context; - - public TimeoutPersistanceWrapper(IPersistTimeouts originalTimeoutPersister, IPersistTimeoutsV2 originalTimeoutPersisterV2, Context context) - { - this.originalTimeoutPersister = originalTimeoutPersister; - this.originalTimeoutPersisterV2 = originalTimeoutPersisterV2; - this.context = context; - } - - public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) - { - return originalTimeoutPersister.GetNextChunk(startSlice, out nextTimeToRunQuery); - } - - public void Add(TimeoutData timeout) - { - originalTimeoutPersister.Add(timeout); - } - - public bool TryRemove(string timeoutId, out TimeoutData timeoutData) - { - return originalTimeoutPersister.TryRemove(timeoutId, out timeoutData); - } - - public void RemoveTimeoutBy(Guid sagaId) - { - originalTimeoutPersister.RemoveTimeoutBy(sagaId); - } - - public TimeoutData Peek(string timeoutId) - { - context.NumberOfProcessingAttempts++; - return originalTimeoutPersisterV2.Peek(timeoutId); - } - - public bool TryRemove(string timeoutId) - { - context.AttemptedToRemoveTimeout = true; - - using (var tx = new TransactionScope(TransactionScopeOption.Suppress)) - { - // delete the timeout so it won't be available on retries - originalTimeoutPersisterV2.TryRemove(timeoutId); - tx.Complete(); - } - - return false; - } - } - } - - [Serializable] - public class MyMessage : IMessage { } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_disabled_dtc.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_disabled_dtc.cs deleted file mode 100644 index e4a7cc1f348..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_disabled_dtc.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NServiceBus.Timeout.Core; - using NUnit.Framework; - - class When_endpoint_uses_outdated_timeout_persistence_with_disabled_dtc : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_not_start_and_show_warning() - { - var context = new Context(); - var scenarioException = Assert.Throws(() => Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Run()) - .InnerException as ScenarioException; - - Assert.IsFalse(context.EndpointsStarted); - Assert.IsNotNull(scenarioException); - StringAssert.Contains("You are using an outdated timeout persistence which can lead to message loss!", scenarioException.InnerException.Message); - } - - [Test] - public void Endpoint_should_start_when_warning_suppressed() - { - var context = new Context(); - - Scenario.Define(context) - .WithEndpoint(c => c.CustomConfig(b => b.SuppressOutdatedTimeoutPersistenceWarning())) - .Done(c => c.EndpointsStarted) - .Run(); - - Assert.IsTrue(context.EndpointsStarted); - } - - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.Transactions().DisableDistributedTransactions(); - }); - } - } - - public class Initalizer : Feature - { - public Initalizer() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_dtc.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_dtc.cs deleted file mode 100644 index 9032ef7474d..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_with_dtc.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_endpoint_uses_outdated_timeout_persistence_with_dtc : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_start() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Run(); - - Assert.IsTrue(context.EndpointsStarted); - } - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.Transactions().EnableDistributedTransactions(); - }); - } - } - - public class Initalizer : Feature - { - public Initalizer() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_without_dtc.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_without_dtc.cs deleted file mode 100644 index 150d54c8c92..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_outdated_timeout_persistence_without_dtc.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NUnit.Framework; - - public class When_endpoint_uses_outdated_timeout_persistence_without_dtc - { - [Test] - public void Endpoint_should_not_start_and_show_warning() - { - var context = new Context(); - var scenarioException = Assert.Throws(() => Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Run()) - .InnerException as ScenarioException; - - Assert.IsFalse(context.EndpointsStarted); - Assert.IsNotNull(scenarioException); - StringAssert.Contains("You are using an outdated timeout persistence which can lead to message loss!", scenarioException.InnerException.Message); - } - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.UseTransport(); - config.OverrideLocalAddress("FakeQueueName"); - }); - } - } - public class Initalizer : Feature - { - public Initalizer() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(() => new OutdatedTimeoutPersister(), DependencyLifecycle.SingleInstance); - } - } - - public class NonDtcTransportDefinition : TransportWithFakeQueues - { - public NonDtcTransportDefinition() - { - HasSupportForDistributedTransactions = false; - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_updated_timeout_persistence.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_updated_timeout_persistence.cs deleted file mode 100644 index cda8be2efbb..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_endpoint_uses_updated_timeout_persistence.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.Features; - using NUnit.Framework; - - class When_endpoint_uses_updated_timeout_persistence : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_start() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Run(); - - Assert.IsTrue(context.EndpointsStarted); - } - - public class Context : ScenarioContext { } - - public class Endpoint : EndpointConfigurationBuilder - { - public Endpoint() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.Transactions().DisableDistributedTransactions(); - }); - } - } - - public class Initalizer : Feature - { - public Initalizer() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Timeouts/When_timeout_storage_is_unavailable_temporarily.cs b/src/NServiceBus.AcceptanceTests/Timeouts/When_timeout_storage_is_unavailable_temporarily.cs deleted file mode 100644 index f5fe442368d..00000000000 --- a/src/NServiceBus.AcceptanceTests/Timeouts/When_timeout_storage_is_unavailable_temporarily.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Timeouts -{ - using System; - using AcceptanceTesting; - using EndpointTemplates; - using Features; - using Timeout.Core; - using NUnit.Framework; - - class When_timeout_storage_is_unavailable_temporarily : NServiceBusAcceptanceTest - { - [Test] - public void Endpoint_should_start() - { - var context = new TestContext(); - - Scenario.Define(context) - .WithEndpoint() - .Done(c => c.EndpointsStarted) - .Run(); - - Assert.IsTrue(context.EndpointsStarted); - } - - - [Test] - public void Endpoint_should_not_shutdown() - { - var context = new TestContext{SecondsToWait = 10}; - var stopTime = DateTime.Now.AddSeconds(45); - - Scenario.Define(context) - .AllowExceptions(ex => ex.Message.Contains("Persister is temporarily unavailable")) - .WithEndpoint(b => - { - b.CustomConfig(busConfig => - { - busConfig.DefineCriticalErrorAction((s, ex) => - { - context.FatalErrorOccurred = true; - }); - }); - }) - .Done(c => context.FatalErrorOccurred || stopTime <= DateTime.Now) - .Run(); - - Assert.IsFalse(context.FatalErrorOccurred, "Circuit breaker was trigged too soon."); - } - - public class TestContext : ScenarioContext - { - public int SecondsToWait { get; set; } - public bool FatalErrorOccurred { get; set; } - } - - [Serializable] - public class MyMessage : IMessage { } - - public class EndpointWithFlakyTimeoutPersister : EndpointConfigurationBuilder - { - public TestContext TestContext { get; set; } - public EndpointWithFlakyTimeoutPersister() - { - EndpointSetup(config => - { - config.EnableFeature(); - config.Transactions().DisableDistributedTransactions(); - config.SuppressOutdatedTimeoutPersistenceWarning(); - }); - } - - class Initalizer : Feature - { - public Initalizer() - { - EnableByDefault(); - } - - protected override void Setup(FeatureConfigurationContext context) - { - context.Container - .ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(tp => tp.SecondsToWait, 10); - } - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/Tx/FakePromotableResourceManager.cs b/src/NServiceBus.AcceptanceTests/Tx/FakePromotableResourceManager.cs index 5a15bada723..4a15fa4795a 100644 --- a/src/NServiceBus.AcceptanceTests/Tx/FakePromotableResourceManager.cs +++ b/src/NServiceBus.AcceptanceTests/Tx/FakePromotableResourceManager.cs @@ -5,8 +5,6 @@ public class FakePromotableResourceManager : IPromotableSinglePhaseNotification, IEnlistmentNotification { - public static Guid ResourceManagerId = Guid.Parse("6f057e24-a0d8-4c95-b091-b8dc9a916fa4"); - public void Prepare(PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); @@ -27,7 +25,6 @@ public void InDoubt(Enlistment enlistment) enlistment.Done(); } - public void Initialize() { } @@ -45,10 +42,8 @@ public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) public byte[] Promote() { return TransactionInterop.GetTransmitterPropagationToken(new CommittableTransaction()); - } - + public static Guid ResourceManagerId = Guid.Parse("6f057e24-a0d8-4c95-b091-b8dc9a916fa4"); } - } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_using_scope_suppress.cs b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_using_scope_suppress.cs new file mode 100644 index 00000000000..ebcb8435bc5 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_using_scope_suppress.cs @@ -0,0 +1,77 @@ +namespace NServiceBus.AcceptanceTests.Tx +{ + using System.Linq; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using EndpointTemplates; + using Logging; + using NUnit.Framework; + using ScenarioDescriptors; + + //note: this test will no longer be relevant in v7 + public class When_requesting_immediate_dispatch_using_scope_suppress : NServiceBusAcceptanceTest + { + [Test] + public Task Should_dispatch_immediately() + { + return Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new InitiatingMessage())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageDispatched) + .Repeat(r => r.For()) + .Should(c => + { + Assert.True(c.MessageDispatched, "Should dispatch the message immediately"); + Assert.True(c.Logs.Any(l => l.Level == LogLevel.Warn && l.Message.Contains("Suppressed ambient transaction detected when requesting the outgoing operation"))); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool MessageDispatched { get; set; } + } + + public class SuppressEndpoint : EndpointConfigurationBuilder + { + public SuppressEndpoint() + { + EndpointSetup(); + } + + public class InitiatingMessageHandler : IHandleMessages + { + public async Task Handle(InitiatingMessage message, IMessageHandlerContext context) + { + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) + { + await context.SendLocal(new MessageToBeDispatchedImmediately()); + } + + throw new SimulatedException(); + } + } + + public class MessageToBeDispatchedImmediatelyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeDispatchedImmediately message, IMessageHandlerContext context) + { + Context.MessageDispatched = true; + return Task.FromResult(0); + } + } + } + + public class InitiatingMessage : ICommand + { + } + + public class MessageToBeDispatchedImmediately : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_least_once.cs b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_least_once.cs new file mode 100644 index 00000000000..8eafd2b3487 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_least_once.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.AcceptanceTests.Tx +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_requesting_immediate_dispatch_with_at_least_once : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_dispatch_immediately() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new InitiatingMessage())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageDispatched) + .Run(); + + Assert.True(context.MessageDispatched, "Should dispatch the message immediately"); + } + + public class Context : ScenarioContext + { + public bool MessageDispatched { get; set; } + } + + public class AtLeastOnceEndpoint : EndpointConfigurationBuilder + { + public AtLeastOnceEndpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport(context.GetTransportType()) + .Transactions(TransportTransactionMode.ReceiveOnly); + }); + } + + public class InitiatingMessageHandler : IHandleMessages + { + public async Task Handle(InitiatingMessage message, IMessageHandlerContext context) + { + var options = new SendOptions(); + + options.RequireImmediateDispatch(); + options.RouteToThisEndpoint(); + + await context.Send(new MessageToBeDispatchedImmediately(), options); + + throw new SimulatedException(); + } + } + + public class MessageToBeDispatchedImmediatelyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeDispatchedImmediately message, IMessageHandlerContext context) + { + Context.MessageDispatched = true; + return Task.FromResult(0); + } + } + } + + public class InitiatingMessage : ICommand + { + } + + public class MessageToBeDispatchedImmediately : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_most_once.cs b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_most_once.cs new file mode 100644 index 00000000000..3e4ba1f3814 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_at_most_once.cs @@ -0,0 +1,70 @@ +namespace NServiceBus.AcceptanceTests.Tx +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_requesting_immediate_dispatch_with_at_most_once : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_dispatch_immediately() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new InitiatingMessage())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageDispatched) + .Run(); + + Assert.True(context.MessageDispatched, "Should dispatch the message immediately"); + } + + public class Context : ScenarioContext + { + public bool MessageDispatched { get; set; } + } + + public class NonTransactionalEndpoint : EndpointConfigurationBuilder + { + public NonTransactionalEndpoint() + { + EndpointSetup((config, context) => { config.UseTransport(context.GetTransportType()).Transactions(TransportTransactionMode.None); }); + } + + public class InitiatingMessageHandler : IHandleMessages + { + public async Task Handle(InitiatingMessage message, IMessageHandlerContext context) + { + var options = new SendOptions(); + + options.RequireImmediateDispatch(); + options.RouteToThisEndpoint(); + + await context.Send(new MessageToBeDispatchedImmediately(), options); + + throw new SimulatedException(); + } + } + + public class MessageToBeDispatchedImmediatelyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeDispatchedImmediately message, IMessageHandlerContext context) + { + Context.MessageDispatched = true; + return Task.FromResult(0); + } + } + } + + public class InitiatingMessage : ICommand + { + } + + public class MessageToBeDispatchedImmediately : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_exactly_once.cs b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_exactly_once.cs new file mode 100644 index 00000000000..2cd10bba401 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Tx/ImmediateDispatch/When_requesting_immediate_dispatch_with_exactly_once.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.AcceptanceTests.Tx +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + + public class When_requesting_immediate_dispatch_with_exactly_once : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_dispatch_immediately() + { + var context = await Scenario.Define() + .WithEndpoint(b => b + .When(session => session.SendLocal(new InitiatingMessage())) + .DoNotFailOnErrorMessages()) + .Done(c => c.MessageDispatched) + .Run(); + + Assert.True(context.MessageDispatched, "Should dispatch the message immediately"); + } + + public class Context : ScenarioContext + { + public bool MessageDispatched { get; set; } + } + + public class ExactlyOnceEndpoint : EndpointConfigurationBuilder + { + public ExactlyOnceEndpoint() + { + //note: We don't have a explicit way to request "ExactlyOnce" yet so we have to rely on it being the default + EndpointSetup(); + } + + public class InitiatingMessageHandler : IHandleMessages + { + public async Task Handle(InitiatingMessage message, IMessageHandlerContext context) + { + var options = new SendOptions(); + + options.RequireImmediateDispatch(); + options.RouteToThisEndpoint(); + + await context.Send(new MessageToBeDispatchedImmediately(), options); + + throw new SimulatedException(); + } + } + + public class MessageToBeDispatchedImmediatelyHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MessageToBeDispatchedImmediately message, IMessageHandlerContext context) + { + Context.MessageDispatched = true; + return Task.FromResult(0); + } + } + } + + public class InitiatingMessage : ICommand + { + } + + public class MessageToBeDispatchedImmediately : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/Issue_2481.cs b/src/NServiceBus.AcceptanceTests/Tx/Issue_2481.cs index 0678b25115a..bbd4336df1e 100644 --- a/src/NServiceBus.AcceptanceTests/Tx/Issue_2481.cs +++ b/src/NServiceBus.AcceptanceTests/Tx/Issue_2481.cs @@ -1,26 +1,26 @@ namespace NServiceBus.AcceptanceTests.Tx { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class Issue_2481 : NServiceBusAcceptanceTest { [Test] - public void Should_enlist_the_receive_in_the_dtc_tx() + public Task Should_enlist_the_receive_in_the_dtc_tx() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .Done(c => c.HandlerInvoked) - .Repeat(r => r.For()) - .Should(c => Assert.False(c.CanEnlistPromotable, "There should exists a DTC tx")) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MyMessage()))) + .Done(c => c.HandlerInvoked) + .Repeat(r => r.For()) + .Should(c => Assert.False(c.CanEnlistPromotable, "There should exists a DTC tx")) + .Run(); } - public class Context : ScenarioContext { public bool HandlerInvoked { get; set; } @@ -32,17 +32,22 @@ public class DTCEndpoint : EndpointConfigurationBuilder { public DTCEndpoint() { - EndpointSetup(c=>c.Transactions().Enable()); + EndpointSetup((config, context) => + { + config.UseTransport(context.GetTransportType()) + .Transactions(TransportTransactionMode.TransactionScope); + }); } public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage messageThatIsEnlisted) + public Task Handle(MyMessage messageThatIsEnlisted, IMessageHandlerContext context) { Context.CanEnlistPromotable = Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager()); Context.HandlerInvoked = true; + return Task.FromResult(0); } } } @@ -51,8 +56,5 @@ public void Handle(MyMessage messageThatIsEnlisted) public class MyMessage : ICommand { } - - - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_disabled.cs b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_disabled.cs deleted file mode 100644 index cf53054eea1..00000000000 --- a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_disabled.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Tx -{ - using System; - using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_receiving_with_dtc_disabled : NServiceBusAcceptanceTest - { - [Test] - public void Should_not_escalate_a_single_durable_rm_to_dtc_tx() - { - - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .Done(c => c.HandlerInvoked) - .Repeat(r => r.For()) - .Should(c => - { - //this check mainly applies to MSMQ who creates a DTC tx right of the bat if DTC is on - Assert.AreEqual(Guid.Empty, c.DistributedIdentifierBefore, "No DTC tx should exist before enlistment"); - Assert.True(c.CanEnlistPromotable, "A promotable RM should be able to enlist"); - }) - .Run(); - } - - public class Context : ScenarioContext - { - public bool HandlerInvoked { get; set; } - - public Guid DistributedIdentifierBefore { get; set; } - - public bool CanEnlistPromotable { get; set; } - } - - public class NonDTCEndpoint : EndpointConfigurationBuilder - { - public NonDTCEndpoint() - { - EndpointSetup(c => c.Transactions() - .DisableDistributedTransactions() - .WrapHandlersExecutionInATransactionScope()); - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(MyMessage messageThatIsEnlisted) - { - Context.DistributedIdentifierBefore = Transaction.Current.TransactionInformation.DistributedIdentifier; - - Context.CanEnlistPromotable = Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager()); - - Context.HandlerInvoked = true; - } - } - } - - [Serializable] - public class MyMessage : ICommand - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_enabled.cs b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_enabled.cs index 9edb610b434..bbd76af3e10 100644 --- a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_enabled.cs +++ b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_dtc_enabled.cs @@ -1,29 +1,30 @@ namespace NServiceBus.AcceptanceTests.Tx { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_receiving_with_dtc_enabled : NServiceBusAcceptanceTest { [Test] - public void Should_enlist_the_receive_in_the_dtc_tx() + public Task Should_enlist_the_receive_in_the_dtc_tx() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .Done(c => c.HandlerInvoked) - .Repeat(r => r.For()) - .Should(c => Assert.False(c.CanEnlistPromotable, "There should exists a DTC tx")) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MyMessage()))) + .Done(c => c.HandlerInvoked) + .Repeat(r => r.For()) + .Should(c => Assert.False(c.CanEnlistPromotable, "There should exists a DTC tx")) + .Run(); } [Test] public void Basic_assumptions_promotable_should_fail_if_durable_already_exists() { - using (var tx = new TransactionScope()) + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { Transaction.Current.EnlistDurable(FakePromotableResourceManager.ResourceManagerId, new FakePromotableResourceManager(), EnlistmentOptions.None); Assert.False(Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager())); @@ -32,11 +33,10 @@ public void Basic_assumptions_promotable_should_fail_if_durable_already_exists() } } - [Test] public void Basic_assumptions_second_promotable_should_fail() { - using (var tx = new TransactionScope()) + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { Assert.True(Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager())); @@ -46,7 +46,6 @@ public void Basic_assumptions_second_promotable_should_fail() } } - public class Context : ScenarioContext { public bool HandlerInvoked { get; set; } @@ -65,10 +64,11 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage messageThatIsEnlisted) + public Task Handle(MyMessage messageThatIsEnlisted, IMessageHandlerContext context) { Context.CanEnlistPromotable = Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager()); Context.HandlerInvoked = true; + return Task.FromResult(0); } } } @@ -77,7 +77,5 @@ public void Handle(MyMessage messageThatIsEnlisted) public class MyMessage : ICommand { } - - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_native_multi_queue_transaction.cs b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_native_multi_queue_transaction.cs new file mode 100644 index 00000000000..c5d752b4a51 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_native_multi_queue_transaction.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.AcceptanceTests.Tx +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NUnit.Framework; + using ScenarioDescriptors; + + public class When_receiving_with_native_multi_queue_transaction : NServiceBusAcceptanceTest + { + [Test] + public Task Should_not_send_outgoing_messages_if_receiving_transaction_is_rolled_back() + { + return Scenario.Define(c => { c.FirstAttempt = true; }) + .WithEndpoint(b => b.When(session => session.SendLocal(new MyMessage()))) + .Done(c => c.MessageHandled) + .Repeat(r => r.For()) + .Should(c => + { + Assert.IsFalse(c.HasFailed); + Assert.IsTrue(c.MessageHandled); + }) + .Run(); + } + + public class Context : ScenarioContext + { + public bool FirstAttempt { get; set; } + public bool MessageHandled { get; set; } + public bool HasFailed { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + config.Recoverability().Immediate(immediate => immediate.NumberOfRetries(1)); + config.UseTransport(context.GetTransportType()) + .Transactions(TransportTransactionMode.ReceiveOnly); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public async Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (TestContext.FirstAttempt) + { + await context.SendLocal(new MessageHandledEvent + { + HasFailed = true + }); + TestContext.FirstAttempt = false; + throw new SimulatedException(); + } + + await context.SendLocal(new MessageHandledEvent()); + } + } + + public class MessageHandledEventHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MessageHandledEvent message, IMessageHandlerContext context) + { + TestContext.MessageHandled = true; + TestContext.HasFailed |= message.HasFailed; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyMessage : ICommand + { + } + + [Serializable] + public class MessageHandledEvent : IMessage + { + public bool HasFailed { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_the_default_settings.cs b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_the_default_settings.cs index 9d6f620cba6..bdbb95b790f 100644 --- a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_the_default_settings.cs +++ b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_the_default_settings.cs @@ -1,23 +1,24 @@ namespace NServiceBus.AcceptanceTests.Tx { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_receiving_with_the_default_settings : NServiceBusAcceptanceTest { [Test] - public void Should_wrap_the_handler_pipeline_with_a_transactionscope() + public Task Should_wrap_the_handler_pipeline_with_a_transactionscope() { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .Done(c => c.HandlerInvoked) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.AmbientTransactionExists, "There should exist an ambient transaction")) - .Run(); + return Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MyMessage()))) + .Done(c => c.HandlerInvoked) + .Repeat(r => r.For()) + .Should(c => Assert.True(c.AmbientTransactionExists, "There should exist an ambient transaction")) + .Run(); } public class Context : ScenarioContext @@ -37,10 +38,11 @@ public class MyMessageHandler : IHandleMessages { public Context Context { get; set; } - public void Handle(MyMessage messageThatIsEnlisted) + public Task Handle(MyMessage messageThatIsEnlisted, IMessageHandlerContext context) { - Context.AmbientTransactionExists = (Transaction.Current != null); + Context.AmbientTransactionExists = Transaction.Current != null; Context.HandlerInvoked = true; + return Task.FromResult(0); } } } diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_transactions_disabled.cs b/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_transactions_disabled.cs deleted file mode 100644 index 4722a79589b..00000000000 --- a/src/NServiceBus.AcceptanceTests/Tx/When_receiving_with_transactions_disabled.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Tx -{ - using System; - using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_receiving_with_transactions_disabled : NServiceBusAcceptanceTest - { - [Test] - public void Should_not_roll_the_message_back_to_the_queue_in_case_of_failure() - { - - Scenario.Define() - .WithEndpoint(b => b.Given(bus => bus.SendLocal(new MyMessage()))) - .AllowExceptions() - .Done(c => c.TestComplete) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.AreEqual(1, c.TimesCalled, "Should not retry the message")) - .Run(); - } - - public class Context : ScenarioContext - { - public bool TestComplete { get; set; } - - public int TimesCalled { get; set; } - } - - public class NonTransactionalEndpoint : EndpointConfigurationBuilder - { - public NonTransactionalEndpoint() - { - EndpointSetup(c => c.Transactions().Disable()); - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - public void Handle(MyMessage message) - { - Context.TimesCalled++; - - using (new TransactionScope(TransactionScopeOption.Suppress)) - { - Bus.SendLocal(new CompleteTest()); - } - - throw new Exception("Simulated exception"); - } - } - - public class CompleteTestHandler : IHandleMessages - { - public Context Context { get; set; } - - public void Handle(CompleteTest message) - { - Context.TestComplete = true; - } - } - } - - [Serializable] - public class MyMessage : ICommand - { - } - - [Serializable] - public class CompleteTest : ICommand - { - } - - - } -} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Tx/When_sending_within_an_ambient_transaction.cs b/src/NServiceBus.AcceptanceTests/Tx/When_sending_within_an_ambient_transaction.cs index 7321f4540fd..57278ed7e91 100644 --- a/src/NServiceBus.AcceptanceTests/Tx/When_sending_within_an_ambient_transaction.cs +++ b/src/NServiceBus.AcceptanceTests/Tx/When_sending_within_an_ambient_transaction.cs @@ -1,60 +1,65 @@ namespace NServiceBus.AcceptanceTests.Tx { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; + using AcceptanceTesting; + using EndpointTemplates; using NUnit.Framework; + using ScenarioDescriptors; public class When_sending_within_an_ambient_transaction : NServiceBusAcceptanceTest { [Test] - public void Should_not_deliver_them_until_the_commit_phase() + public async Task Should_not_deliver_them_until_the_commit_phase() { - Scenario.Define() - .WithEndpoint(b => b.Given((bus, context) => + await Scenario.Define() + .WithEndpoint(b => b.When(async (session, context) => + { + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + await session.Send(new MessageThatIsEnlisted { - using (var tx = new TransactionScope()) - { - bus.Send(new MessageThatIsEnlisted { SequenceNumber = 1 }); - bus.Send(new MessageThatIsEnlisted { SequenceNumber = 2 }); - - //send another message as well so that we can check the order in the receiver - using (new TransactionScope(TransactionScopeOption.Suppress)) - { - bus.Send(new MessageThatIsNotEnlisted()); - } - - tx.Complete(); - } - })) - .Done(c => c.MessageThatIsNotEnlistedHandlerWasCalled && c.TimesCalled >= 2) - .Repeat(r => r.For()) - .Should(c => Assert.AreEqual(1, c.SequenceNumberOfFirstMessage,"The transport should preserve the order in which the transactional messages are delivered to the queuing system")) - .Run(); - } + SequenceNumber = 1 + }); + await session.Send(new MessageThatIsEnlisted + { + SequenceNumber = 2 + }); - [Test] - public void Should_not_deliver_them_on_rollback() - { - Scenario.Define() - .WithEndpoint(b => b.Given(bus => + //send another message as well so that we can check the order in the receiver + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - using (new TransactionScope()) - { - bus.Send(new MessageThatIsEnlisted()); + await session.Send(new MessageThatIsNotEnlisted()); + } - //rollback - } + tx.Complete(); + } + })) + .Done(c => c.MessageThatIsNotEnlistedHandlerWasCalled && c.TimesCalled >= 2) + .Repeat(r => r.For()) + .Should(c => Assert.AreEqual(1, c.SequenceNumberOfFirstMessage, "The transport should preserve the order in which the transactional messages are delivered to the queuing system")) + .Run(); + } - bus.Send(new MessageThatIsNotEnlisted()); + [Test] + public async Task Should_not_deliver_them_on_rollback() + { + await Scenario.Define() + .WithEndpoint(b => b.When(async session => + { + using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + await session.Send(new MessageThatIsEnlisted()); + //rollback + } - })) - .Done(c => c.MessageThatIsNotEnlistedHandlerWasCalled) - .Repeat(r => r.For()) - .Should(c => Assert.False(c.MessageThatIsEnlistedHandlerWasCalled, "The transactional handler should not be called")) - .Run(); + await session.Send(new MessageThatIsNotEnlisted()); + })) + .Done(c => c.MessageThatIsNotEnlistedHandlerWasCalled) + .Repeat(r => r.For()) + .Should(c => Assert.False(c.MessageThatIsEnlistedHandlerWasCalled, "The transactional handler should not be called")) + .Run(); } public class Context : ScenarioContext @@ -73,7 +78,7 @@ public class TransactionalEndpoint : EndpointConfigurationBuilder { public TransactionalEndpoint() { - EndpointSetup() + EndpointSetup(c => c.LimitMessageProcessingConcurrencyTo(1)) .AddMapping(typeof(TransactionalEndpoint)) .AddMapping(typeof(TransactionalEndpoint)); } @@ -82,7 +87,7 @@ public class MessageThatIsEnlistedHandler : IHandleMessages().AddOrReplaceInstances(Guid.NewGuid().ToString(), instances); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Versioning/When_multiple_versions_of_a_message_is_published.cs b/src/NServiceBus.AcceptanceTests/Versioning/When_multiple_versions_of_a_message_is_published.cs index 10f440bf149..748d0532a41 100644 --- a/src/NServiceBus.AcceptanceTests/Versioning/When_multiple_versions_of_a_message_is_published.cs +++ b/src/NServiceBus.AcceptanceTests/Versioning/When_multiple_versions_of_a_message_is_published.cs @@ -1,37 +1,48 @@ namespace NServiceBus.AcceptanceTests.Versioning { - using EndpointTemplates; + using System.Threading.Tasks; using AcceptanceTesting; - using NServiceBus.Features; + using EndpointTemplates; + using Features; using NUnit.Framework; - using PubSub; + using Routing; public class When_multiple_versions_of_a_message_is_published : NServiceBusAcceptanceTest { [Test] - public void Should_deliver_is_to_both_v1_and_vX_subscribers() + public async Task Should_deliver_is_to_both_v1_and_vX_subscribers() { - Scenario.Define() - .WithEndpoint(b => - b.When(c => c.V1Subscribed && c.V2Subscribed, (bus, c) => bus.Publish(e => - { - e.SomeData = 1; - e.MoreInfo = "dasd"; - }))) - .WithEndpoint(b => b.Given((bus,c) => - { - bus.Subscribe(); - if (c.HasNativePubSubSupport) - c.V1Subscribed = true; - })) - .WithEndpoint(b => b.Given((bus,c) => + var context = await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.V1Subscribed && c.V2Subscribed, (session, c) => + { + return session.Publish(e => { - bus.Subscribe(); - if (c.HasNativePubSubSupport) - c.V2Subscribed = true; - })) - .Done(c => c.V1SubscriberGotTheMessage && c.V2SubscriberGotTheMessage) - .Run(); + e.SomeData = 1; + e.MoreInfo = "dasd"; + }); + })) + .WithEndpoint(b => b.When(async (session, c) => + { + await session.Subscribe(); + if (c.HasNativePubSubSupport) + { + c.V1Subscribed = true; + } + })) + .WithEndpoint(b => b.When(async (session, c) => + { + await session.Subscribe(); + if (c.HasNativePubSubSupport) + { + c.V2Subscribed = true; + } + })) + .Done(c => c.V1SubscriberGotTheMessage && c.V2SubscriberGotTheMessage) + .Run(); + + Assert.True(context.V1SubscriberGotTheMessage); + Assert.True(context.V2SubscriberGotTheMessage); } public class Context : ScenarioContext @@ -51,18 +62,19 @@ public V2Publisher() { EndpointSetup(b => b.OnEndpointSubscribed((s, context) => { - if (s.SubscriberReturnAddress.Queue.Contains("V1Subscriber")) + if (s.SubscriberReturnAddress.Contains("V1Subscriber")) { context.V1Subscribed = true; } - if (s.SubscriberReturnAddress.Queue.Contains("V2Subscriber")) + if (s.SubscriberReturnAddress.Contains("V2Subscriber")) { context.V2Subscribed = true; } })); } } + public class V1Subscriber : EndpointConfigurationBuilder { public V1Subscriber() @@ -70,49 +82,48 @@ public V1Subscriber() EndpointSetup(b => b.DisableFeature()) .ExcludeType() .AddMapping(typeof(V2Publisher)); - } - - class V1Handler:IHandleMessages + class V1Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(V1Event message) + + public Task Handle(V1Event message, IMessageHandlerContext context) { Context.V1SubscriberGotTheMessage = true; + return Task.FromResult(0); } } } - public class V2Subscriber : EndpointConfigurationBuilder { public V2Subscriber() { EndpointSetup(b => b.DisableFeature()) - .AddMapping(typeof(V2Publisher)); + .AddMapping(typeof(V2Publisher)); } class V2Handler : IHandleMessages { public Context Context { get; set; } - public void Handle(V2Event message) + public Task Handle(V2Event message, IMessageHandlerContext context) { Context.V2SubscriberGotTheMessage = true; + return Task.FromResult(0); } } } - - public interface V1Event : IEvent + public class V1Event : IEvent { - int SomeData { get; set; } + public int SomeData { get; set; } } - public interface V2Event : V1Event + public class V2Event : V1Event { - string MoreInfo { get; set; } + public string MoreInfo { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Volatile/When_sending_to_non_durable_endpoint.cs b/src/NServiceBus.AcceptanceTests/Volatile/When_sending_to_non_durable_endpoint.cs deleted file mode 100644 index 4f414e187b9..00000000000 --- a/src/NServiceBus.AcceptanceTests/Volatile/When_sending_to_non_durable_endpoint.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Volatile -{ - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTests.EndpointTemplates; - using NServiceBus.AcceptanceTests.ScenarioDescriptors; - using NUnit.Framework; - - public class When_sending_to_non_durable_endpoint: NServiceBusAcceptanceTest - { - [Test] - public void Should_receive_the_message() - { - var context = new Context(); - Scenario.Define(context) - .WithEndpoint(b => b.Given((bus, c) => bus.Send(new MyMessage()))) - .WithEndpoint() - .Done(c => c.WasCalled) - .Repeat(r => r.For(Transports.Default)) - .Should(c => Assert.True(c.WasCalled, "The message handler should be called")) - .Run(); - } - - public class Context : ScenarioContext - { - public bool WasCalled { get; set; } - } - - public class Sender : EndpointConfigurationBuilder - { - public Sender() - { - EndpointSetup(builder => - { - builder.DisableDurableMessages(); - builder.DiscardFailedMessagesInsteadOfSendingToErrorQueue(); // to avoid creating the error q, it might blow up for brokers (RabbitMQ) - }) - .AddMapping(typeof(Receiver)); - } - } - - public class Receiver : EndpointConfigurationBuilder - { - public Receiver() - { - EndpointSetup(builder => - { - builder.DisableDurableMessages(); - builder.DiscardFailedMessagesInsteadOfSendingToErrorQueue(); // to avoid creating the error q, it might blow up for brokers (RabbitMQ) - }); - } - } - - public class MyMessage : IMessage - { - } - - public class MyMessageHandler : IHandleMessages - { - public Context Context { get; set; } - - public IBus Bus { get; set; } - - public void Handle(MyMessage message) - { - Context.WasCalled = true; - } - } - } -} diff --git a/src/NServiceBus.AcceptanceTests/packages.config b/src/NServiceBus.AcceptanceTests/packages.config index 3590354a6dd..d7eadaa2952 100644 --- a/src/NServiceBus.AcceptanceTests/packages.config +++ b/src/NServiceBus.AcceptanceTests/packages.config @@ -1,11 +1,6 @@  - - - - - - - - + + + \ No newline at end of file diff --git a/src/NServiceBus.ContainerTests/NServiceBus.ContainerTests.csproj b/src/NServiceBus.ContainerTests/NServiceBus.ContainerTests.csproj index feffbca0bf6..a57e4350bcb 100644 --- a/src/NServiceBus.ContainerTests/NServiceBus.ContainerTests.csproj +++ b/src/NServiceBus.ContainerTests/NServiceBus.ContainerTests.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,7 +10,7 @@ Properties NServiceBus.ContainerTests NServiceBus.ContainerTests - v4.5 + v4.5.2 512 ..\ @@ -40,8 +40,9 @@ false - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True @@ -52,7 +53,6 @@ - @@ -67,14 +67,17 @@ + + + - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - - - + + + \ No newline at end of file diff --git a/src/NServiceBus.ContainerTests/TestContainerBuilder.cs b/src/NServiceBus.ContainerTests/TestContainerBuilder.cs index dc493c5fc0e..a468cc91ddf 100644 --- a/src/NServiceBus.ContainerTests/TestContainerBuilder.cs +++ b/src/NServiceBus.ContainerTests/TestContainerBuilder.cs @@ -1,11 +1,11 @@ namespace NServiceBus.ContainerTests { using System; - using NServiceBus.ObjectBuilder.Common; + using ObjectBuilder.Common; public static class TestContainerBuilder { - public static Func ConstructBuilder = () => (IContainer)Activator.CreateInstance(Type.GetType("NServiceBus.ObjectBuilder.Autofac.AutofacObjectBuilder,NServiceBus.Core")); + public static Func ConstructBuilder = () => (IContainer)Activator.CreateInstance(Type.GetType("NServiceBus.AutofacObjectBuilder,NServiceBus.Core")); } } \ No newline at end of file diff --git a/src/NServiceBus.ContainerTests/When_building_components.cs b/src/NServiceBus.ContainerTests/When_building_components.cs index cb6889a36d3..d45e96fe7c8 100644 --- a/src/NServiceBus.ContainerTests/When_building_components.cs +++ b/src/NServiceBus.ContainerTests/When_building_components.cs @@ -1,7 +1,7 @@ namespace NServiceBus.ContainerTests { using NServiceBus; - using NServiceBus.ObjectBuilder.Common; + using ObjectBuilder.Common; using NUnit.Framework; [TestFixture] @@ -28,25 +28,31 @@ public void Singlecall_components_should_yield_unique_instances() } [Test] - public void UoW_components_should_resolve_from_main_container() + public void UoW_components_should_yield_the_same_instance() { using (var builder = TestContainerBuilder.ConstructBuilder()) { InitializeBuilder(builder); - Assert.NotNull(builder.Build(typeof(InstancePerUoWComponent))); + + var instance1 = builder.Build(typeof(InstancePerUoWComponent)); + var instance2 = builder.Build(typeof(InstancePerUoWComponent)); + + Assert.AreSame(instance1, instance2); } - //Not supported by typeof(WindsorObjectBuilder)); } [Test] - public void Lambda_uow_components_should_resolve_from_main_container() + public void Lambda_uow_components_should_yield_the_same_instance() { using (var builder = TestContainerBuilder.ConstructBuilder()) { InitializeBuilder(builder); - Assert.NotNull(builder.Build(typeof(LambdaComponentUoW))); + + var instance1 = builder.Build(typeof(LambdaComponentUoW)); + var instance2 = builder.Build(typeof(LambdaComponentUoW)); + + Assert.AreSame(instance1, instance2); } - //Not supported by typeof(WindsorObjectBuilder)); } [Test] @@ -128,6 +134,16 @@ public class SingletonComponent { } + public interface ISingletonComponentWithPropertyDependency + { + + } + + public class SingletonComponentWithPropertyDependency : ISingletonComponentWithPropertyDependency + { + public SingletonComponent Dependency { get; set; } + } + public class SinglecallComponent { } @@ -169,7 +185,7 @@ public ComponentWithBothConstructorAndSetterInjection(ConstructorDependency cons ConstructorDependency = constructorDependency; } - public ConstructorDependency ConstructorDependency { get; private set; } + public ConstructorDependency ConstructorDependency { get; } public SetterDependency SetterDependency { get; set; } } diff --git a/src/NServiceBus.ContainerTests/When_disposing_the_builder.cs b/src/NServiceBus.ContainerTests/When_disposing_the_builder.cs index 3c58a49286b..9cfd013b632 100644 --- a/src/NServiceBus.ContainerTests/When_disposing_the_builder.cs +++ b/src/NServiceBus.ContainerTests/When_disposing_the_builder.cs @@ -35,28 +35,6 @@ public void When_circular_ref_exists_between_container_and_builder_should_not_in builder.Dispose(); } - [Test] - public void Should_dispose_all_IDisposable_components_in_child_container() - { - using (var main = TestContainerBuilder.ConstructBuilder()) - { - DisposableComponent.DisposeCalled = false; - AnotherSingletonComponent.DisposeCalled = false; - - main.RegisterSingleton(typeof(AnotherSingletonComponent), new AnotherSingletonComponent()); - main.Configure(typeof(DisposableComponent), DependencyLifecycle.InstancePerUnitOfWork); - - using (var builder = main.BuildChildContainer()) - { - builder.Build(typeof(DisposableComponent)); - } - Assert.False(AnotherSingletonComponent.DisposeCalled, "Dispose should not be called on AnotherSingletonComponent because it belongs to main container"); - Assert.True(DisposableComponent.DisposeCalled, "Dispose should be called on DisposableComponent"); - } - - //Not supported by, typeof(SpringObjectBuilder)); - } - public class DisposableComponent : IDisposable { public static bool DisposeCalled; diff --git a/src/NServiceBus.ContainerTests/When_querying_for_registered_components.cs b/src/NServiceBus.ContainerTests/When_querying_for_registered_components.cs index e93099028d5..ccb9f76b208 100644 --- a/src/NServiceBus.ContainerTests/When_querying_for_registered_components.cs +++ b/src/NServiceBus.ContainerTests/When_querying_for_registered_components.cs @@ -1,11 +1,11 @@ namespace NServiceBus.ContainerTests { using NServiceBus; - using NServiceBus.ObjectBuilder.Common; + using ObjectBuilder.Common; using NUnit.Framework; [TestFixture] - public class When_querying_for_registered_components + public class When_querying_for_registered_components { [Test] public void Existing_components_should_return_true() diff --git a/src/NServiceBus.ContainerTests/When_registering_components.cs b/src/NServiceBus.ContainerTests/When_registering_components.cs index 4712df6b033..f627681a0f8 100644 --- a/src/NServiceBus.ContainerTests/When_registering_components.cs +++ b/src/NServiceBus.ContainerTests/When_registering_components.cs @@ -27,7 +27,7 @@ public void Should_support_lambdas_that_uses_other_components_registered_later() { using (var builder = TestContainerBuilder.ConstructBuilder()) { - builder.Configure(() => ((StaticFactory) builder.Build(typeof(StaticFactory))).Create(), DependencyLifecycle.InstancePerCall); + builder.Configure(() => ((StaticFactory)builder.Build(typeof(StaticFactory))).Create(), DependencyLifecycle.InstancePerCall); builder.Configure(() => new StaticFactory(), DependencyLifecycle.SingleInstance); Assert.NotNull(builder.Build(typeof(ComponentCreatedByFactory))); @@ -48,19 +48,6 @@ public void A_registration_should_be_allowed_to_be_updated() //Not supported by, typeof(SpringObjectBuilder)); } - [Test] - [Explicit] - public void A_registration_should_update_default_component_for_interface() - { - using (var builder = TestContainerBuilder.ConstructBuilder()) - { - builder.Configure(typeof(SomeClass), DependencyLifecycle.InstancePerCall); - builder.Configure(typeof(SomeOtherClass), DependencyLifecycle.InstancePerCall); - - Assert.IsInstanceOf(builder.Build(typeof(ISomeInterface))); - } - } - [Test] public void Register_singleton_should_be_supported() { @@ -85,7 +72,7 @@ public void Registering_the_same_singleton_for_different_interfaces_should_be_su builder.Configure(typeof(ComponentThatDependsOnMultiSingletons), DependencyLifecycle.InstancePerCall); - var dependency = (ComponentThatDependsOnMultiSingletons) builder.Build(typeof(ComponentThatDependsOnMultiSingletons)); + var dependency = (ComponentThatDependsOnMultiSingletons)builder.Build(typeof(ComponentThatDependsOnMultiSingletons)); Assert.NotNull(dependency.Singleton1); Assert.NotNull(dependency.Singleton2); @@ -98,70 +85,33 @@ public void Registering_the_same_singleton_for_different_interfaces_should_be_su } [Test] - public void Properties_set_on_duplicate_registrations_should_not_be_discarded() - { - using (var builder = TestContainerBuilder.ConstructBuilder()) - { - builder.Configure(typeof(DuplicateClass), DependencyLifecycle.SingleInstance); - builder.ConfigureProperty(typeof(DuplicateClass), "SomeProperty", true); - - builder.Configure(typeof(DuplicateClass), DependencyLifecycle.SingleInstance); - builder.ConfigureProperty(typeof(DuplicateClass), "AnotherProperty", true); - - var component = (DuplicateClass) builder.Build(typeof(DuplicateClass)); - Assert.True(component.SomeProperty); - - Assert.True(component.AnotherProperty); - } - } - - [Test] - public void Properties_configured_multiple_times_should_retain_only_the_last_configuration() - { - using (var builder = TestContainerBuilder.ConstructBuilder()) - { - builder.Configure(typeof(DuplicateClass), DependencyLifecycle.SingleInstance); - builder.ConfigureProperty(typeof(DuplicateClass), "SomeProperty", false); - builder.ConfigureProperty(typeof(DuplicateClass), "SomeProperty", true); // this should remove/override the previous property setting - - var component = (DuplicateClass) builder.Build(typeof(DuplicateClass)); - Assert.True(component.SomeProperty); - } - } - - - [Test] - public void Setter_dependencies_should_be_supported() + public void Setter_dependencies_should_be_supported_when_resolving_interfaces() { using (var builder = TestContainerBuilder.ConstructBuilder()) { builder.Configure(typeof(SomeClass), DependencyLifecycle.InstancePerCall); builder.Configure(typeof(ClassWithSetterDependencies), DependencyLifecycle.SingleInstance); - builder.ConfigureProperty(typeof(ClassWithSetterDependencies), "EnumDependency", SomeEnum.X); - builder.ConfigureProperty(typeof(ClassWithSetterDependencies), "SimpleDependency", 1); - builder.ConfigureProperty(typeof(ClassWithSetterDependencies), "StringDependency", "Test"); - - var component = (ClassWithSetterDependencies) builder.Build(typeof(ClassWithSetterDependencies)); - Assert.AreEqual(component.EnumDependency, SomeEnum.X); - Assert.AreEqual(component.SimpleDependency, 1); - Assert.AreEqual(component.StringDependency, "Test"); + + var component = (ClassWithSetterDependencies)builder.Build(typeof(IWithSetterDependencies)); Assert.NotNull(component.ConcreteDependency, "Concrete classed should be property injected"); Assert.NotNull(component.InterfaceDependency, "Interfaces should be property injected"); Assert.NotNull(component.concreteDependencyWithSetOnly, "Set only properties should be supported"); } } + [Test] - public void Setter_dependencies_should_override_container_defaults() + public void Setter_injection_should_be_enabled_by_default() { using (var builder = TestContainerBuilder.ConstructBuilder()) { builder.Configure(typeof(SomeClass), DependencyLifecycle.InstancePerCall); builder.Configure(typeof(ClassWithSetterDependencies), DependencyLifecycle.SingleInstance); - builder.ConfigureProperty(typeof(ClassWithSetterDependencies), "InterfaceDependency", new SomeOtherClass()); - var component = (ClassWithSetterDependencies) builder.Build(typeof(ClassWithSetterDependencies)); - Assert.IsInstanceOf(typeof(SomeOtherClass), component.InterfaceDependency, "Explicitly set dependency should be injected, not container's default type"); + var component = (ClassWithSetterDependencies)builder.Build(typeof(ClassWithSetterDependencies)); + Assert.NotNull(component.ConcreteDependency, "Concrete classed should be property injected"); + Assert.NotNull(component.InterfaceDependency, "Interfaces should be property injected"); + Assert.NotNull(component.concreteDependencyWithSetOnly, "Set only properties should be supported"); } } @@ -206,8 +156,6 @@ public void All_implemented_interfaces_should_be_registered_for_func() Assert.True(builder.HasComponent(typeof(IYetAnotherInterface))); Assert.AreEqual(1, builder.BuildAll(typeof(IYetAnotherInterface)).Count()); } - - //Not supported by typeof(SpringObjectBuilder)); } [Test] @@ -342,11 +290,12 @@ public class DuplicateClass public bool AnotherProperty { get; set; } } - public class ClassWithSetterDependencies + public interface IWithSetterDependencies + { + } + + public class ClassWithSetterDependencies : IWithSetterDependencies { - public SomeEnum EnumDependency { get; set; } - public int SimpleDependency { get; set; } - public string StringDependency { get; set; } public ISomeInterface InterfaceDependency { get; set; } public SomeClass ConcreteDependency { get; set; } diff --git a/src/NServiceBus.ContainerTests/When_releasing_components.cs b/src/NServiceBus.ContainerTests/When_releasing_components.cs deleted file mode 100644 index 4b96e8f6568..00000000000 --- a/src/NServiceBus.ContainerTests/When_releasing_components.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus.ContainerTests -{ - using System; - using NServiceBus; - using NUnit.Framework; - - [TestFixture] - public class When_releasing_components - { - [Test] - public void Transient_component_should_be_destructed_called() - { - using (var builder = TestContainerBuilder.ConstructBuilder()) - { - builder.Configure(typeof(TransientClass), DependencyLifecycle.InstancePerCall); - - var comp = (TransientClass) builder.Build(typeof(TransientClass)); - comp.Name = "Jon"; - - var weak = new WeakReference(comp); - - builder.Release(comp); - // ReSharper disable once RedundantAssignment - comp = null; - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.IsFalse(weak.IsAlive); - Assert.IsTrue(TransientClass.Destructed); - } - - } - - public class TransientClass - { - public static bool Destructed; - - public string Name { get; set; } - - ~TransientClass() - { - Destructed = true; - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.ContainerTests/When_using_nested_containers.cs b/src/NServiceBus.ContainerTests/When_using_nested_containers.cs index ce590656e3f..8fec2c621a7 100644 --- a/src/NServiceBus.ContainerTests/When_using_nested_containers.cs +++ b/src/NServiceBus.ContainerTests/When_using_nested_containers.cs @@ -1,15 +1,11 @@ namespace NServiceBus.ContainerTests { using System; - using System.Diagnostics; - using System.Threading.Tasks; - using NServiceBus; using NUnit.Framework; [TestFixture] public class When_using_nested_containers { - [Test] public void Instance_per_uow__components_should_be_disposed_when_the_child_container_is_disposed() { @@ -18,101 +14,77 @@ public void Instance_per_uow__components_should_be_disposed_when_the_child_conta builder.Configure(typeof(InstancePerUoWComponent), DependencyLifecycle.InstancePerUnitOfWork); using (var nestedContainer = builder.BuildChildContainer()) + { nestedContainer.Build(typeof(InstancePerUoWComponent)); + } Assert.True(InstancePerUoWComponent.DisposeCalled); } - //Not supported by typeof(SpringObjectBuilder)); } [Test] - public void Instance_per_uow__components_should_not_be_shared_across_child_containers() + public void Instance_per_uow_components_should_yield_different_instances_between_parent_and_child_containers() { using (var builder = TestContainerBuilder.ConstructBuilder()) { - builder.Configure(typeof(InstancePerUoWComponent), - DependencyLifecycle.InstancePerUnitOfWork); + builder.Configure(typeof(InstancePerUoWComponent), DependencyLifecycle.InstancePerUnitOfWork); - var task1 = Task.Factory.StartNew(() => - { - using (var childContainer = builder.BuildChildContainer()) - { - return childContainer.Build(typeof(InstancePerUoWComponent)); - } - }); - var task2 = Task.Factory.StartNew(() => - { - using (var childContainer = builder.BuildChildContainer()) - { - return childContainer.Build(typeof(InstancePerUoWComponent)); - } - }); - Assert.AreNotSame(task1.Result, task2.Result); - } + var parentInstance = builder.Build(typeof(InstancePerUoWComponent)); + using (var childContainer = builder.BuildChildContainer()) + { + var childInstance = childContainer.Build(typeof(InstancePerUoWComponent)); - //Not supported by typeof(SpringObjectBuilder)); + Assert.AreNotSame(parentInstance, childInstance); + } + } } [Test] - public void Instance_per_call_components_should_not_be_shared_across_child_containers() + public void Instance_per_uow_components_should_yield_different_instances_between_different_instances_of_child_containers() { using (var builder = TestContainerBuilder.ConstructBuilder()) { - builder.Configure(typeof(InstancePerCallComponent), DependencyLifecycle.InstancePerCall); + builder.Configure(typeof(InstancePerUoWComponent), DependencyLifecycle.InstancePerUnitOfWork); object instance1; using (var nestedContainer = builder.BuildChildContainer()) { - instance1 = nestedContainer.Build(typeof(InstancePerCallComponent)); + instance1 = nestedContainer.Build(typeof(InstancePerUoWComponent)); } object instance2; using (var anotherNestedContainer = builder.BuildChildContainer()) { - instance2 = anotherNestedContainer.Build(typeof(InstancePerCallComponent)); + instance2 = anotherNestedContainer.Build(typeof(InstancePerUoWComponent)); } Assert.AreNotSame(instance1, instance2); } - } - [Test, Explicit("Time consuming")] - public void Instance_per_call_components_should_not_cause_memory_leaks() + [Test] + public void Instance_per_call_components_should_not_be_shared_across_child_containers() { - const int iterations = 20000; - using (var builder = TestContainerBuilder.ConstructBuilder()) { - builder.Configure(typeof(InstancePerCallComponent), DependencyLifecycle.InstancePerUnitOfWork); - - GC.Collect(); - var before = GC.GetTotalMemory(true); - var sw = Stopwatch.StartNew(); + builder.Configure(typeof(InstancePerCallComponent), DependencyLifecycle.InstancePerCall); - for (var i = 0; i < iterations; i++) + object instance1; + using (var nestedContainer = builder.BuildChildContainer()) { - using (var nestedContainer = builder.BuildChildContainer()) - { - nestedContainer.Build(typeof(InstancePerCallComponent)); - } + instance1 = nestedContainer.Build(typeof(InstancePerCallComponent)); } - sw.Stop(); - // Collect all generations of memory. - GC.Collect(); - - var after = GC.GetTotalMemory(true); - Console.WriteLine("{0} Time: {1} MemDelta: {2} bytes", builder.GetType().Name, sw.Elapsed, after - before); - - var upperLimitBytes = 200*1024; - Assert.That(after - before, Is.LessThan(upperLimitBytes), "Apparently {0} consumed more than {1} KB of memory", builder, upperLimitBytes/1024); + object instance2; + using (var anotherNestedContainer = builder.BuildChildContainer()) + { + instance2 = anotherNestedContainer.Build(typeof(InstancePerCallComponent)); + } + Assert.AreNotSame(instance1, instance2); } - - //Not supported by, typeof(NinjectObjectBuilder)); } [Test] - public void UoW_components_in_the_parent_container_should_be_singletons_in_the_child_container() + public void UoW_components_in_the_parent_container_should_be_singletons_in_the_same_child_container() { using (var builder = TestContainerBuilder.ConstructBuilder()) { @@ -120,15 +92,16 @@ public void UoW_components_in_the_parent_container_should_be_singletons_in_the_c using (var nestedContainer = builder.BuildChildContainer()) { - Assert.AreSame(nestedContainer.Build(typeof(InstancePerUoWComponent)), nestedContainer.Build(typeof(InstancePerUoWComponent)), "UoW's should be singleton in child container"); + var instance1 = nestedContainer.Build(typeof(InstancePerUoWComponent)); + var instance2 = nestedContainer.Build(typeof(InstancePerUoWComponent)); + + Assert.AreSame(instance1, instance2, "UoW's should be singleton in child container"); } } - //Not supported by typeof(SpringObjectBuilder)); } [Test] - [Explicit] - public void UoW_components_should_by_instance_per_call_in_root_container() + public void UoW_components_built_on_root_container_should_be_singletons_even_with_child_builder_present() { using (var builder = TestContainerBuilder.ConstructBuilder()) { @@ -139,10 +112,10 @@ public void UoW_components_should_by_instance_per_call_in_root_container() //no-op } - Assert.AreNotSame(builder.Build(typeof(InstancePerUoWComponent)), builder.Build(typeof(InstancePerUoWComponent)), "UoW's should be instance per call in the root container"); + var instance1 = builder.Build(typeof(InstancePerUoWComponent)); + var instance2 = builder.Build(typeof(InstancePerUoWComponent)); + Assert.AreSame(instance1, instance2, "UoW's should be singletons in the root container"); } - - //Not supported by typeof(AutofacObjectBuilder), typeof(WindsorObjectBuilder)); } [Test] @@ -161,22 +134,54 @@ public void Should_not_dispose_singletons_when_container_goes_out_of_scope() } Assert.False(SingletonComponent.DisposeCalled); } - //Not supported by typeof(SpringObjectBuilder)); } - class SingletonComponent : ISingletonComponent, IDisposable + [Test] + public void Should_dispose_all_non_percall_IDisposable_components_in_child_container() { - public static bool DisposeCalled; + using (var main = TestContainerBuilder.ConstructBuilder()) + { + DisposableComponent.DisposeCalled = false; + AnotherDisposableComponent.DisposeCalled = false; + main.RegisterSingleton(typeof(AnotherDisposableComponent), new AnotherDisposableComponent()); + main.Configure(typeof(DisposableComponent), DependencyLifecycle.InstancePerUnitOfWork); + + using (var builder = main.BuildChildContainer()) + { + builder.Build(typeof(DisposableComponent)); + } + Assert.False(AnotherDisposableComponent.DisposeCalled, "Dispose should not be called on AnotherSingletonComponent because it belongs to main container"); + Assert.True(DisposableComponent.DisposeCalled, "Dispose should be called on DisposableComponent"); + } + + //Not supported by, typeof(SpringObjectBuilder)); + } + + public interface IInstanceToReplaceInNested + { + } + + public class InstanceToReplaceInNested_Parent : IInstanceToReplaceInNested + { + } + + public class InstanceToReplaceInNested_Child : IInstanceToReplaceInNested + { + } + + class SingletonComponent : ISingletonComponent, IDisposable + { public void Dispose() { DisposeCalled = true; } + + public static bool DisposeCalled; } class ComponentThatDependsOfSingleton { - } } @@ -189,13 +194,12 @@ public void Dispose() public class InstancePerUoWComponent : IDisposable { - public static bool DisposeCalled; - public void Dispose() { DisposeCalled = true; } + public static bool DisposeCalled; } public class SingletonComponent : ISingletonComponent @@ -209,4 +213,24 @@ public class AnotherSingletonComponent : ISingletonComponent public interface ISingletonComponent { } + + public class DisposableComponent : IDisposable + { + public static bool DisposeCalled; + + public void Dispose() + { + DisposeCalled = true; + } + } + + public class AnotherDisposableComponent : IDisposable + { + public static bool DisposeCalled; + + public void Dispose() + { + DisposeCalled = true; + } + } } \ No newline at end of file diff --git a/src/NServiceBus.ContainerTests/packages.config b/src/NServiceBus.ContainerTests/packages.config index de006b3ce21..d7eadaa2952 100644 --- a/src/NServiceBus.ContainerTests/packages.config +++ b/src/NServiceBus.ContainerTests/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests.x86/NServiceBus.Core.x86.Tests.csproj b/src/NServiceBus.Core.Tests.x86/NServiceBus.Core.x86.Tests.csproj index 10ff1783239..a7333092e65 100644 --- a/src/NServiceBus.Core.Tests.x86/NServiceBus.Core.x86.Tests.csproj +++ b/src/NServiceBus.Core.Tests.x86/NServiceBus.Core.x86.Tests.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,7 +9,7 @@ Properties NServiceBus.Core.Tests.x32 NServiceBus.Core.Tests.x32 - v4.5 + v4.5.2 512 ..\ @@ -39,11 +39,9 @@ - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - ..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True @@ -68,6 +66,9 @@ - + + + + \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests.x86/packages.config b/src/NServiceBus.Core.Tests.x86/packages.config index bc237b0abad..bebf20b6ce3 100644 --- a/src/NServiceBus.Core.Tests.x86/packages.config +++ b/src/NServiceBus.Core.Tests.x86/packages.config @@ -1,5 +1,4 @@  - - + \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/API/APIApprovals.ApproveNServiceBus.approved.txt b/src/NServiceBus.Core.Tests/API/APIApprovals.ApproveNServiceBus.approved.txt new file mode 100644 index 00000000000..3bc5a87c89c --- /dev/null +++ b/src/NServiceBus.Core.Tests/API/APIApprovals.ApproveNServiceBus.approved.txt @@ -0,0 +1,3609 @@ +[assembly: NServiceBus.ReleaseDateAttribute("2016-10-05", "2016-10-05")] +[assembly: System.CLSCompliantAttribute(true)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.AcceptanceTesting, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.Core.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007f16e21368ff041183fab592d9e8ed37e7be355e93323147a1d29983d6e591b04282e4da0c9e18bd901e112c0033925eb7d7872c2f1706655891c5c9d57297994f707d16ee9a8f40d978f064ee1ffc73c0db3f4712691b23bf596f75130f4ec978cf78757ec034625a5f27e6bb50c618931ea49f6f628fd74271c32959efb1c5")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.Hosting.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.PerformanceTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007f16e21368ff041183fab592d9e8ed37e7be355e93323147a1d29983d6e591b04282e4da0c9e18bd901e112c0033925eb7d7872c2f1706655891c5c9d57297994f707d16ee9a8f40d978f064ee1ffc73c0db3f4712691b23bf596f75130f4ec978cf78757ec034625a5f27e6bb50c618931ea49f6f628fd74271c32959efb1c5")] +[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] +[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.5.2", FrameworkDisplayName=".NET Framework 4.5.2")] +public class static ConfigureHandlerSettings +{ + [System.ObsoleteAttribute("Setting property values explicitly is no longer supported via this API. Use `.Con" + + "figureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full contro" + + "l over handler creation. Will be removed in version 7.0.0.", true)] + public static void InitializeHandlerProperty(this NServiceBus.EndpointConfiguration config, string property, object value) { } +} +namespace NServiceBus +{ + [System.ObsoleteAttribute("Use the string based overloads. Will be removed in version 7.0.0.", true)] + public class Address + { + public Address() { } + } + public enum AddressMode + { + Local = 0, + Remote = 1, + } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExcludeAssemblies` instead. Will be removed in version" + + " 7.0.0.", true)] + public class AllAssemblies + { + public AllAssemblies() { } + } + public class static AuditConfigReader + { + public static bool TryGetAuditQueueAddress(this NServiceBus.Settings.ReadOnlySettings settings, out string address) { } + } + public class static AutoSubscribeSettingsExtensions + { + public static NServiceBus.AutomaticSubscriptions.Config.AutoSubscribeSettings AutoSubscribe(this NServiceBus.EndpointConfiguration config) { } + } + public class static BestPracticesOptionExtensions + { + public static void DoNotEnforceBestPractices(this NServiceBus.Extensibility.ExtendableOptions options) { } + public static void DoNotEnforceBestPractices(this NServiceBus.Pipeline.IOutgoingReplyContext context) { } + public static void DoNotEnforceBestPractices(this NServiceBus.Pipeline.IOutgoingSendContext context) { } + public static void DoNotEnforceBestPractices(this NServiceBus.Pipeline.ISubscribeContext context) { } + public static void DoNotEnforceBestPractices(this NServiceBus.Pipeline.IOutgoingPublishContext context) { } + public static void DoNotEnforceBestPractices(this NServiceBus.Pipeline.IUnsubscribeContext context) { } + public static bool IgnoredBestPractices(this NServiceBus.Extensibility.ExtendableOptions options) { } + } + public class static Bus + { + [System.ObsoleteAttribute("Use `Endpoint.Create` instead. The member currently throws a NotImplementedExcept" + + "ion. Will be removed in version 7.0.0.", true)] + public static NServiceBus.IStartableBus Create(NServiceBus.EndpointConfiguration configuration) { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.SendOnly` instead. The member currently throws a NotIm" + + "plementedException. Will be removed in version 7.0.0.", true)] + public static NServiceBus.IBus CreateSendOnly(NServiceBus.EndpointConfiguration configuration) { } + } + [System.ObsoleteAttribute("No longer used, use the new callbacks api described in the version 6 upgrade guid" + + "e. Will be removed in version 7.0.0.", true)] + public class BusAsyncResultEventArgs + { + public BusAsyncResultEventArgs() { } + } + [System.ObsoleteAttribute("Use `EndpointConfiguration` instead. Will be removed in version 7.0.0.", true)] + public class BusConfiguration + { + public BusConfiguration() { } + } + [System.ObsoleteAttribute("Use `Notifications` instead. Will be removed in version 7.0.0.", true)] + public class BusNotifications + { + public BusNotifications() { } + } + [System.ObsoleteAttribute("Replaced by NServiceBus.Callbacks package. Will be removed in version 7.0.0.", true)] + public class CompletionResult + { + public CompletionResult() { } + } + public class static ConfigurationTimeoutExtensions + { + public static void TimeToWaitBeforeTriggeringCriticalErrorOnTimeoutOutages(this NServiceBus.EndpointConfiguration config, System.TimeSpan timeToWait) { } + } + [System.ObsoleteAttribute("This is no longer a public API. Will be removed in version 7.0.0.", true)] + public class Configure + { + public Configure() { } + } + public class static ConfigureAudit + { + public static void AuditProcessedMessagesTo(this NServiceBus.EndpointConfiguration config, string auditQueue, System.Nullable timeToBeReceived = null) { } + } + public class static ConfigureCriticalErrorAction + { + public static void DefineCriticalErrorAction(this NServiceBus.EndpointConfiguration endpointConfiguration, System.Func onCriticalError) { } + [System.ObsoleteAttribute("Use `ConfigureCriticalErrorAction.DefineCriticalErrorAction(EndpointConfiguration" + + ", Func)` instead. Will be removed in version 7.0.0." + + "", true)] + public static void DefineCriticalErrorAction(this NServiceBus.EndpointConfiguration endpointConfiguration, System.Action onCriticalError) { } + } + public class static ConfigureError + { + public static void SendFailedMessagesTo(this NServiceBus.EndpointConfiguration config, string errorQueue) { } + } + public class static ConfigureFileShareDataBus + { + public static NServiceBus.DataBus.DataBusExtensions BasePath(this NServiceBus.DataBus.DataBusExtensions config, string basePath) { } + [System.ObsoleteAttribute("Use `BasePath(this DataBusExtensions config, string basePath)` " + + "instead. The member currently throws a NotImplementedException. Will be removed " + + "in version 7.0.0.", true)] + public static NServiceBus.DataBus.DataBusExtentions BasePath(this NServiceBus.DataBus.DataBusExtentions config, string basePath) { } + } + public class static ConfigureForwarding + { + public static void ForwardReceivedMessagesTo(this NServiceBus.EndpointConfiguration config, string address) { } + } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public class static ConfigureInMemoryFaultManagement + { + [System.ObsoleteAttribute("This is no longer supported. If you want full control over what happens when a me" + + "ssage fails (including retries) override the MoveFaultsToErrorQueue behavior. Th" + + "e member currently throws a NotImplementedException. Will be removed in version " + + "7.0.0.", true)] + public static void DiscardFailedMessagesInsteadOfSendingToErrorQueue(this NServiceBus.EndpointConfiguration config) { } + } + public class static ConfigureLicenseExtensions + { + public static void License(this NServiceBus.EndpointConfiguration config, string licenseText) { } + public static void LicensePath(this NServiceBus.EndpointConfiguration config, string licenseFile) { } + } + public class static ConfigurePurging + { + public static void PurgeOnStartup(this NServiceBus.EndpointConfiguration config, bool value) { } + [System.ObsoleteAttribute("The member currently throws a NotImplementedException. Will be removed in version" + + " 7.0.0.", true)] + public static bool PurgeOnStartup(this NServiceBus.Configure config) { } + } + public class static ConfigureQueueCreation + { + [System.ObsoleteAttribute("Use `CreateQueues` instead. The member currently throws a NotImplementedException" + + ". Will be removed in version 7.0.0.", true)] + public static bool CreateQueues(this NServiceBus.Configure config) { } + public static bool CreateQueues(this NServiceBus.Settings.ReadOnlySettings settings) { } + public static void DoNotCreateQueues(this NServiceBus.EndpointConfiguration config) { } + } + public class static ConfigureRijndaelEncryptionService + { + public static void RegisterEncryptionService(this NServiceBus.EndpointConfiguration config, System.Func func) { } + [System.ObsoleteAttribute(@"It is no longer possible to access the builder to create an encryption service. If container access is required use the container directly in the factory. Use `RegisterEncryptionService(this EndpointConfiguration config, Func func)` instead. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static void RegisterEncryptionService(this NServiceBus.EndpointConfiguration config, System.Func func) { } + public static void RijndaelEncryptionService(this NServiceBus.EndpointConfiguration config) { } + public static void RijndaelEncryptionService(this NServiceBus.EndpointConfiguration config, string encryptionKeyIdentifier, byte[] encryptionKey, System.Collections.Generic.IList decryptionKeys = null) { } + public static void RijndaelEncryptionService(this NServiceBus.EndpointConfiguration config, string encryptionKeyIdentifier, System.Collections.Generic.IDictionary keys, System.Collections.Generic.IList decryptionKeys = null) { } + } + public class static ConfigureTransportConnectionString + { + [System.ObsoleteAttribute("Not available any more. The member currently throws a NotImplementedException. Wi" + + "ll be removed in version 7.0.0.", true)] + public static string TransportConnectionString(this NServiceBus.Configure config) { } + } + public class static ConnectorContextExtensions + { + public static NServiceBus.Pipeline.IAuditContext CreateAuditContext(this NServiceBus.Pipeline.ForkConnector forkConnector, NServiceBus.Transport.OutgoingMessage message, string auditAddress, NServiceBus.Pipeline.IIncomingPhysicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IBatchDispatchContext CreateBatchDispatchContext(this NServiceBus.Pipeline.StageForkConnector stageForkConnector, System.Collections.Generic.IReadOnlyCollection transportOperations, NServiceBus.Pipeline.IIncomingPhysicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IDispatchContext CreateDispatchContext(this NServiceBus.Pipeline.StageConnector stageConnector, System.Collections.Generic.IReadOnlyCollection transportOperations, NServiceBus.Pipeline.IBatchDispatchContext sourceContext) { } + public static NServiceBus.Pipeline.IDispatchContext CreateDispatchContext(this NServiceBus.Pipeline.StageConnector stageConnector, System.Collections.Generic.IReadOnlyCollection transportOperations, NServiceBus.Pipeline.IRoutingContext sourceContext) { } + public static NServiceBus.Pipeline.IForwardingContext CreateForwardingContext(this NServiceBus.Pipeline.ForkConnector forwardingContext, NServiceBus.Transport.OutgoingMessage message, string forwardingAddress, NServiceBus.Pipeline.IIncomingPhysicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IIncomingLogicalMessageContext CreateIncomingLogicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Pipeline.LogicalMessage logicalMessage, NServiceBus.Pipeline.IIncomingPhysicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IIncomingPhysicalMessageContext CreateIncomingPhysicalMessageContext(this NServiceBus.Pipeline.StageForkConnector stageForkConnector, NServiceBus.Transport.IncomingMessage incomingMessage, NServiceBus.Pipeline.ITransportReceiveContext sourceContext) { } + public static NServiceBus.Pipeline.IIncomingPhysicalMessageContext CreateIncomingPhysicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Transport.IncomingMessage incomingMessage, NServiceBus.Pipeline.ITransportReceiveContext sourceContext) { } + public static NServiceBus.Pipeline.IInvokeHandlerContext CreateInvokeHandlerContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Pipeline.MessageHandler messageHandler, NServiceBus.Persistence.CompletableSynchronizedStorageSession storageSession, NServiceBus.Pipeline.IIncomingLogicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Pipeline.OutgoingLogicalMessage outgoingMessage, System.Collections.Generic.IReadOnlyCollection routingStrategies, NServiceBus.Pipeline.IOutgoingPublishContext sourceContext) { } + public static NServiceBus.Pipeline.IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Pipeline.OutgoingLogicalMessage outgoingMessage, System.Collections.Generic.IReadOnlyCollection routingStrategies, NServiceBus.Pipeline.IOutgoingReplyContext sourceContext) { } + public static NServiceBus.Pipeline.IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Pipeline.OutgoingLogicalMessage outgoingMessage, System.Collections.Generic.IReadOnlyCollection routingStrategies, NServiceBus.Pipeline.IOutgoingSendContext sourceContext) { } + public static NServiceBus.Pipeline.IOutgoingPhysicalMessageContext CreateOutgoingPhysicalMessageContext(this NServiceBus.Pipeline.StageConnector stageConnector, byte[] messageBody, System.Collections.Generic.IReadOnlyCollection routingStrategies, NServiceBus.Pipeline.IOutgoingLogicalMessageContext sourceContext) { } + public static NServiceBus.Pipeline.IRoutingContext CreateRoutingContext(this NServiceBus.Pipeline.ForkConnector forkConnector, NServiceBus.Transport.OutgoingMessage outgoingMessage, string localAddress, NServiceBus.Pipeline.ITransportReceiveContext sourceContext) { } + public static NServiceBus.Pipeline.IRoutingContext CreateRoutingContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Transport.OutgoingMessage outgoingMessage, NServiceBus.Routing.RoutingStrategy routingStrategy, NServiceBus.Pipeline.IForwardingContext sourceContext) { } + public static NServiceBus.Pipeline.IRoutingContext CreateRoutingContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Transport.OutgoingMessage outgoingMessage, NServiceBus.Routing.RoutingStrategy routingStrategy, NServiceBus.Pipeline.IAuditContext sourceContext) { } + public static NServiceBus.Pipeline.IRoutingContext CreateRoutingContext(this NServiceBus.Pipeline.StageConnector stageConnector, NServiceBus.Transport.OutgoingMessage outgoingMessage, System.Collections.Generic.IReadOnlyCollection routingStrategies, NServiceBus.Pipeline.IOutgoingPhysicalMessageContext sourceContext) { } + } + public abstract class ContainSagaData : NServiceBus.IContainSagaData + { + protected ContainSagaData() { } + public virtual System.Guid Id { get; set; } + public virtual string OriginalMessageId { get; set; } + public virtual string Originator { get; set; } + } + public class static ContentTypes + { + public const string Json = "application/json"; + public const string Xml = "text/xml"; + } + public class Conventions + { + public Conventions() { } + public void AddSystemMessagesConventions(System.Func definesMessageType) { } + [System.ObsoleteAttribute("No longer an extension point. The member currently throws a NotImplementedExcepti" + + "on. Will be removed in version 7.0.0.", true)] + public System.TimeSpan GetTimeToBeReceived(System.Type messageType) { } + public bool IsCommandType(System.Type t) { } + public bool IsDataBusProperty(System.Reflection.PropertyInfo property) { } + public bool IsEncryptedProperty(System.Reflection.PropertyInfo property) { } + public bool IsEventType(System.Type t) { } + [System.ObsoleteAttribute("No longer an extension point. The member currently throws a NotImplementedExcepti" + + "on. Will be removed in version 7.0.0.", true)] + public static bool IsExpressMessageType(System.Type t) { } + public bool IsInSystemConventionList(System.Type t) { } + public bool IsMessageType(System.Type t) { } + } + public class ConventionsBuilder : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public ConventionsBuilder(NServiceBus.Settings.SettingsHolder settings) { } + public NServiceBus.Conventions Conventions { get; } + public NServiceBus.ConventionsBuilder DefiningCommandsAs(System.Func definesCommandType) { } + public NServiceBus.ConventionsBuilder DefiningDataBusPropertiesAs(System.Func definesDataBusProperty) { } + public NServiceBus.ConventionsBuilder DefiningEncryptedPropertiesAs(System.Func definesEncryptedProperty) { } + public NServiceBus.ConventionsBuilder DefiningEventsAs(System.Func definesEventType) { } + public NServiceBus.ConventionsBuilder DefiningMessagesAs(System.Func definesMessageType) { } + } + public class static CorrelationContextExtensions + { + public static string GetCorrelationId(this NServiceBus.SendOptions options) { } + public static string GetCorrelationId(this NServiceBus.ReplyOptions options) { } + public static void SetCorrelationId(this NServiceBus.SendOptions options, string correlationId) { } + public static void SetCorrelationId(this NServiceBus.ReplyOptions options, string correlationId) { } + } + public class CriticalError + { + public CriticalError(System.Func onCriticalErrorAction) { } + public virtual void Raise(string errorMessage, System.Exception exception) { } + } + public class CriticalErrorContext : NServiceBus.ICriticalErrorContext + { + public CriticalErrorContext(System.Func stop, string error, System.Exception exception) { } + public string Error { get; } + public System.Exception Exception { get; } + public System.Func Stop { get; } + } + public class static CriticalTimeMonitoringConfig + { + public static void EnableCriticalTimePerformanceCounter(this NServiceBus.EndpointConfiguration config) { } + } + public class DataBusProperty : NServiceBus.IDataBusProperty, System.Runtime.Serialization.ISerializable + where T : class + { + public DataBusProperty(T value) { } + protected DataBusProperty(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public bool HasValue { get; set; } + public string Key { get; set; } + public T Value { get; } + public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public object GetValue() { } + public void SetValue(object valueToSet) { } + } + public class static DateTimeExtensions + { + public static System.DateTime ToUtcDateTime(string wireFormattedString) { } + public static string ToWireFormattedString(System.DateTime dateTime) { } + } + public class static DefaultRecoverabilityPolicy + { + public static NServiceBus.RecoverabilityAction Invoke(NServiceBus.RecoverabilityConfig config, NServiceBus.Transport.ErrorContext errorContext) { } + } + public class DelayedConfig + { + public DelayedConfig(int maxNumberOfRetries, System.TimeSpan timeIncrease) { } + public int MaxNumberOfRetries { get; } + public System.TimeSpan TimeIncrease { get; } + } + public class static DelayedDeliveryOptionExtensions + { + public static void DelayDeliveryWith(this NServiceBus.SendOptions options, System.TimeSpan delay) { } + public static void DoNotDeliverBefore(this NServiceBus.SendOptions options, System.DateTimeOffset at) { } + public static System.Nullable GetDeliveryDate(this NServiceBus.SendOptions options) { } + public static System.Nullable GetDeliveryDelay(this NServiceBus.SendOptions options) { } + } + public class DelayedRetriesSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public NServiceBus.DelayedRetriesSettings NumberOfRetries(int numberOfRetries) { } + public NServiceBus.DelayedRetriesSettings TimeIncrease(System.TimeSpan timeIncrease) { } + } + public sealed class DelayedRetry : NServiceBus.RecoverabilityAction + { + public System.TimeSpan Delay { get; } + } + public enum DependencyLifecycle + { + SingleInstance = 0, + InstancePerUnitOfWork = 1, + InstancePerCall = 2, + } + public class DistributionPolicy : NServiceBus.IDistributionPolicy + { + public DistributionPolicy() { } + public void SetDistributionStrategy(NServiceBus.Routing.DistributionStrategy distributionStrategy) { } + } + public enum DistributionStrategyScope + { + Send = 0, + Publish = 1, + } + public class static DurableMessagesConfig + { + public static void DisableDurableMessages(this NServiceBus.EndpointConfiguration config) { } + public static bool DurableMessagesEnabled(this NServiceBus.Settings.ReadOnlySettings settings) { } + [System.ObsoleteAttribute("Use `DurableMessagesEnabled` instead. The member currently throws a NotImplemente" + + "dException. Will be removed in version 7.0.0.", true)] + public static bool DurableMessagesEnabled(this NServiceBus.Configure config) { } + public static void EnableDurableMessages(this NServiceBus.EndpointConfiguration config) { } + } + public class static DurableMessagesConventionExtensions + { + public static NServiceBus.ConventionsBuilder DefiningExpressMessagesAs(this NServiceBus.ConventionsBuilder builder, System.Func definesExpressMessageType) { } + } + public class EncryptedValue + { + public EncryptedValue() { } + public string Base64Iv { get; set; } + public string EncryptedBase64Value { get; set; } + } + public class static Endpoint + { + public static System.Threading.Tasks.Task Create(NServiceBus.EndpointConfiguration configuration) { } + public static async System.Threading.Tasks.Task Start(NServiceBus.EndpointConfiguration configuration) { } + } + public class EndpointConfiguration : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public EndpointConfiguration(string endpointName) { } + public NServiceBus.Notifications Notifications { get; } + [System.ObsoleteAttribute("Use `EndpointConfiguration.AddHeaderToAllOutgoingMessages(string key,string value" + + ")` instead. The member currently throws a NotImplementedException. Will be remov" + + "ed in version 7.0.0.", true)] + public System.Collections.Generic.IDictionary OutgoingHeaders { get; } + public NServiceBus.Pipeline.PipelineSettings Pipeline { get; } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExcludeAssemblies` instead. The member currently throw" + + "s a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void AssembliesToScan(System.Collections.Generic.IEnumerable assemblies) { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExcludeAssemblies` instead. The member currently throw" + + "s a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void AssembliesToScan(params System.Reflection.Assembly[] assemblies) { } + public NServiceBus.ConventionsBuilder Conventions() { } + public void CustomConfigurationSource(NServiceBus.Config.ConfigurationSource.IConfigurationSource configurationSource) { } + [System.ObsoleteAttribute("Endpoint name is now a mandatory constructor argument on EndpointConfiguration. T" + + "he member currently throws a NotImplementedException. Will be removed in version" + + " 7.0.0.", true)] + public void EndpointName(string name) { } + public void ExcludeAssemblies(params string[] assemblies) { } + public void ExcludeTypes(params System.Type[] types) { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.OverridePublicReturnAddress(string address)` instead. " + + "The member currently throws a NotImplementedException. Will be removed in versio" + + "n 7.0.0.", true)] + public void OverridePublicReturnAddress(NServiceBus.Address address) { } + public void RegisterComponents(System.Action registration) { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExcludeAssemblies` instead. The member currently throw" + + "s a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void ScanAssembliesInDirectory(string probeDirectory) { } + public void ScanAssembliesInNestedDirectories() { } + public void SendOnly() { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExcludeTypes` instead. The member currently throws a N" + + "otImplementedException. Will be removed in version 7.0.0.", true)] + public void TypesToScan(System.Collections.Generic.IEnumerable typesToScan) { } + public void UseContainer(System.Action customizations = null) + where T : NServiceBus.Container.ContainerDefinition, new () { } + public void UseContainer(System.Type definitionType) { } + public void UseContainer(NServiceBus.ObjectBuilder.Common.IContainer builder) { } + } + public class static EndpointConfigurationExtensions + { + public static void DisableFeature(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Features.Feature { } + public static void DisableFeature(this NServiceBus.EndpointConfiguration config, System.Type featureType) { } + public static void EnableFeature(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Features.Feature { } + public static void EnableFeature(this NServiceBus.EndpointConfiguration config, System.Type featureType) { } + } + public class static EndpointInstanceExtensions + { + public static NServiceBus.Routing.EndpointInstance AtMachine(this NServiceBus.Routing.EndpointInstance instance, string machineName) { } + } + public class static ErrorQueueSettings + { + public static string ErrorQueueAddress(this NServiceBus.Settings.ReadOnlySettings settings) { } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface | System.AttributeTargets.All)] + public sealed class ExpressAttribute : System.Attribute + { + public ExpressAttribute() { } + } + [System.ObsoleteAttribute("Headers are not managed via the send, reply and publishoptions. Will be removed i" + + "n version 7.0.0.", true)] + public class static ExtensionMethods + { + [System.ObsoleteAttribute("Use a incoming behavior to get access to the current message. The member currentl" + + "y throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static object CurrentMessageBeingHandled { get; set; } + [System.ObsoleteAttribute("Headers are not \'set\' only on the outgoing pipeline. The member currently throws " + + "a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static string GetMessageHeader(this NServiceBus.IBus bus, object msg, string key) { } + [System.ObsoleteAttribute("Use the overload of the Send, Publish or Reply method that accepts an options par" + + "ameter. Call options.SetHeader(\"MyHeader\",\"MyValue\") instead. The member current" + + "ly throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static void SetMessageHeader(this NServiceBus.IBus bus, object msg, string key, string value) { } + } + public class FailedConfig + { + public FailedConfig(string errorQueue) { } + public string ErrorQueue { get; } + } + public class FileShareDataBus : NServiceBus.DataBus.DataBusDefinition + { + public FileShareDataBus() { } + protected internal override System.Type ProvidedByFeature() { } + } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExecuteTheseHandlersFirst` instead. Will be removed in" + + " version 7.0.0.", true)] + public class First + { + public First() { } + } + public class static HeaderOptionExtensions + { + public static System.Collections.Generic.IReadOnlyDictionary GetHeaders(this NServiceBus.Extensibility.ExtendableOptions options) { } + public static void SetHeader(this NServiceBus.Extensibility.ExtendableOptions options, string key, string value) { } + } + public class static Headers + { + public const string ContentType = "NServiceBus.ContentType"; + public const string ControlMessageHeader = "NServiceBus.ControlMessage"; + public const string ConversationId = "NServiceBus.ConversationId"; + public const string CorrelationId = "NServiceBus.CorrelationId"; + public const string DelayedRetries = "NServiceBus.Retries"; + public const string DelayedRetriesTimestamp = "NServiceBus.Retries.Timestamp"; + public const string DestinationSites = "NServiceBus.DestinationSites"; + public const string EnclosedMessageTypes = "NServiceBus.EnclosedMessageTypes"; + [System.ObsoleteAttribute("Use `ImmediateRetries` instead. Will be removed in version 7.0.0.", true)] + public const string FLRetries = "NServiceBus.FLRetries"; + public const string HasLicenseExpired = "$.diagnostics.license.expired"; + public const string HeaderName = "Header"; + public const string HostDisplayName = "$.diagnostics.hostdisplayname"; + public const string HostId = "$.diagnostics.hostid"; + public const string HttpFrom = "NServiceBus.From"; + public const string HttpTo = "NServiceBus.To"; + public const string ImmediateRetries = "NServiceBus.FLRetries"; + public const string IsDeferredMessage = "NServiceBus.IsDeferredMessage"; + public const string IsSagaTimeoutMessage = "NServiceBus.IsSagaTimeoutMessage"; + public const string MessageId = "NServiceBus.MessageId"; + public const string MessageIntent = "NServiceBus.MessageIntent"; + public const string NonDurableMessage = "NServiceBus.NonDurableMessage"; + public const string NServiceBusVersion = "NServiceBus.Version"; + public const string OriginatingAddress = "NServiceBus.OriginatingAddress"; + public const string OriginatingEndpoint = "NServiceBus.OriginatingEndpoint"; + public const string OriginatingHostId = "$.diagnostics.originating.hostid"; + public const string OriginatingMachine = "NServiceBus.OriginatingMachine"; + public const string OriginatingSagaId = "NServiceBus.OriginatingSagaId"; + public const string OriginatingSagaType = "NServiceBus.OriginatingSagaType"; + public const string OriginatingSite = "NServiceBus.OriginatingSite"; + public const string ProcessingEnded = "NServiceBus.ProcessingEnded"; + public const string ProcessingEndpoint = "NServiceBus.ProcessingEndpoint"; + public const string ProcessingMachine = "NServiceBus.ProcessingMachine"; + public const string ProcessingStarted = "NServiceBus.ProcessingStarted"; + public const string RelatedTo = "NServiceBus.RelatedTo"; + public const string ReplyToAddress = "NServiceBus.ReplyToAddress"; + [System.ObsoleteAttribute("Use `DelayedRetries` instead. Will be removed in version 7.0.0.", true)] + public const string Retries = "NServiceBus.Retries"; + [System.ObsoleteAttribute("Use `DelayedRetriesTimestamp` instead. Will be removed in version 7.0.0.", true)] + public const string RetriesTimestamp = "NServiceBus.Retries.Timestamp"; + public const string ReturnMessageErrorCodeHeader = "NServiceBus.ReturnMessage.ErrorCode"; + public const string RijndaelKeyIdentifier = "NServiceBus.RijndaelKeyIdentifier"; + public const string RouteTo = "NServiceBus.Header.RouteTo"; + public const string SagaId = "NServiceBus.SagaId"; + public const string SagaType = "NServiceBus.SagaType"; + public const string SubscriberEndpoint = "NServiceBus.SubscriberEndpoint"; + public const string SubscriberTransportAddress = "NServiceBus.SubscriberAddress"; + public const string SubscriptionMessageType = "SubscriptionMessageType"; + public const string TimeSent = "NServiceBus.TimeSent"; + public const string TimeToBeReceived = "NServiceBus.TimeToBeReceived"; + [System.ObsoleteAttribute("The WinIdName header is no longer attached to outgoing message to avoid passing s" + + "ecurity related information on the wire. Should you rely on the header being pre" + + "sent you can add a message mutator that sets it. Will be removed in version 7.0." + + "0.", true)] + public const string WindowsIdentityName = "WinIdName"; + } + public class static HostInfoConfigurationExtensions + { + public static NServiceBus.HostInfoSettings UniquelyIdentifyRunningInstance(this NServiceBus.EndpointConfiguration config) { } + } + public class HostInfoSettings + { + public NServiceBus.HostInfoSettings UsingCustomDisplayName(string displayName) { } + public NServiceBus.HostInfoSettings UsingCustomIdentifier(System.Guid id) { } + public NServiceBus.HostInfoSettings UsingInstalledFilePath() { } + public NServiceBus.HostInfoSettings UsingNames(string instanceName, string hostName) { } + } + public interface IAmStartedByMessages : NServiceBus.IHandleMessages { } + [System.ObsoleteAttribute("Use `config.UseTransport().SubscriptionAuthorizer(Authorizer);` in" + + "stead. Will be removed in version 7.0.0.", true)] + public interface IAuthorizeSubscriptions { } + [System.ObsoleteAttribute(@"IHandleMessages now exposes the IMessageHandlerContext parameter. Use this to access what used to be available in the IBus interface. Use the provided context in extension points like message handlers or IEndpointInstance when outside the message processing pipeline. Will be removed in version 7.0.0.", true)] + public interface IBus { } + public class static IBusExtensions + { + [System.ObsoleteAttribute("Use `IMessageHandlerContext.DoNotContinueDispatchingCurrentMessageToHandlers()` p" + + "rovided to message handlers instead. The member currently throws a NotImplemente" + + "dException. Will be removed in version 7.0.0.", true)] + public static void DoNotContinueDispatchingCurrentMessageToHandlers(this NServiceBus.IBus bus) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.ForwardCurrentMessageTo(string destination)` provided" + + " to message handlers instead. The member currently throws a NotImplementedExcept" + + "ion. Will be removed in version 7.0.0.", true)] + public static void ForwardCurrentMessageTo(this NServiceBus.IBus bus, string destination) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.HandleCurrentMessageLater()` provided to message hand" + + "lers instead. The member currently throws a NotImplementedException. Will be rem" + + "oved in version 7.0.0.", true)] + public static void HandleCurrentMessageLater(this NServiceBus.IBus bus) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.Reply(object message)` provided to message handlers i" + + "nstead. The member currently throws a NotImplementedException. Will be removed i" + + "n version 7.0.0.", true)] + public static void Reply(this NServiceBus.IBus bus, object message) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.Reply(Action messageConstructor)` provided to m" + + "essage handlers instead. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + public static void Reply(this NServiceBus.IBus bus, System.Action messageConstructor) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.SendLocal(object message)` provided to message handle" + + "rs instead. The member currently throws a NotImplementedException. Will be remov" + + "ed in version 7.0.0.", true)] + public static void SendLocal(this NServiceBus.IBus bus, object message) { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext.SendLocal(Action messageConstructor)` provided " + + "to message handlers instead. The member currently throws a NotImplementedExcepti" + + "on. Will be removed in version 7.0.0.", true)] + public static void SendLocal(this NServiceBus.IBus bus, System.Action messageConstructor) { } + [System.ObsoleteAttribute("Use `Subscribe(Type messageType)` instead. The member currently throws a NotImple" + + "mentedException. Will be removed in version 7.0.0.", true)] + public static void Subscribe(this NServiceBus.IBus bus, System.Type messageType) { } + [System.ObsoleteAttribute("Use `Subscribe()` instead. The member currently throws a NotImplementedExcepti" + + "on. Will be removed in version 7.0.0.", true)] + public static void Subscribe(this NServiceBus.IBus bus) { } + [System.ObsoleteAttribute("Use `Unsubscribe(Type messageType)` instead. The member currently throws a NotImp" + + "lementedException. Will be removed in version 7.0.0.", true)] + public static void Unsubscribe(this NServiceBus.IBus bus, System.Type messageType) { } + [System.ObsoleteAttribute("Use `Unsubscribe()` instead. The member currently throws a NotImplementedExcep" + + "tion. Will be removed in version 7.0.0.", true)] + public static void Unsubscribe(this NServiceBus.IBus bus) { } + } + [System.ObsoleteAttribute("Replaced by NServiceBus.Callbacks package. Will be removed in version 7.0.0.", true)] + public interface ICallback { } + public interface ICommand : NServiceBus.IMessage { } + public interface IConfigureHowToFindSagaWithMessage + { + void ConfigureMapping(System.Linq.Expressions.Expression> sagaEntityProperty, System.Linq.Expressions.Expression> messageProperty) + where TSagaEntity : NServiceBus.IContainSagaData + ; + } + public interface IContainSagaData + { + System.Guid Id { get; set; } + string OriginalMessageId { get; set; } + string Originator { get; set; } + } + public interface ICriticalErrorContext + { + string Error { get; } + System.Exception Exception { get; } + System.Func Stop { get; } + } + public interface IDataBusProperty + { + bool HasValue { get; set; } + string Key { get; set; } + object GetValue(); + void SetValue(object value); + } + public interface IEncryptionService + { + string Decrypt(NServiceBus.EncryptedValue encryptedValue, NServiceBus.Pipeline.IIncomingLogicalMessageContext context); + NServiceBus.EncryptedValue Encrypt(string value, NServiceBus.Pipeline.IOutgoingLogicalMessageContext context); + } + public interface IEndpointInstance : NServiceBus.IMessageSession + { + System.Threading.Tasks.Task Stop(); + } + public interface IEvent : NServiceBus.IMessage { } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public interface IExcludesBuilder { } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IHandleMessages + { + System.Threading.Tasks.Task Handle(T message, NServiceBus.IMessageHandlerContext context); + } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IHandleTimeouts + { + System.Threading.Tasks.Task Timeout(T state, NServiceBus.IMessageHandlerContext context); + } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public interface IIncludesBuilder { } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public interface IManageMessageHeaders { } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IMessage { } + [System.ObsoleteAttribute("Use `IMessageHandlerContext` provided to message handlers instead. Will be remove" + + "d in version 7.0.0.", true)] + public interface IMessageContext { } + public interface IMessageCreator + { + T CreateInstance(); + T CreateInstance(System.Action action); + object CreateInstance(System.Type messageType); + } + public interface IMessageHandlerContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IMessageProcessingContext, NServiceBus.IPipelineContext + { + NServiceBus.Persistence.SynchronizedStorageSession SynchronizedStorageSession { get; } + void DoNotContinueDispatchingCurrentMessageToHandlers(); + System.Threading.Tasks.Task HandleCurrentMessageLater(); + } + public interface IMessageProcessingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext + { + System.Collections.Generic.IReadOnlyDictionary MessageHeaders { get; } + string MessageId { get; } + string ReplyToAddress { get; } + System.Threading.Tasks.Task ForwardCurrentMessageTo(string destination); + System.Threading.Tasks.Task Reply(object message, NServiceBus.ReplyOptions options); + System.Threading.Tasks.Task Reply(System.Action messageConstructor, NServiceBus.ReplyOptions options); + } + public class static IMessageProcessingContextExtensions + { + public static System.Threading.Tasks.Task Reply(this NServiceBus.IMessageProcessingContext context, object message) { } + public static System.Threading.Tasks.Task Reply(this NServiceBus.IMessageProcessingContext context, System.Action messageConstructor) { } + } + public interface IMessageSession + { + System.Threading.Tasks.Task Publish(object message, NServiceBus.PublishOptions options); + System.Threading.Tasks.Task Publish(System.Action messageConstructor, NServiceBus.PublishOptions publishOptions); + System.Threading.Tasks.Task Send(object message, NServiceBus.SendOptions options); + System.Threading.Tasks.Task Send(System.Action messageConstructor, NServiceBus.SendOptions options); + System.Threading.Tasks.Task Subscribe(System.Type eventType, NServiceBus.SubscribeOptions options); + System.Threading.Tasks.Task Unsubscribe(System.Type eventType, NServiceBus.UnsubscribeOptions options); + } + public class static IMessageSessionExtensions + { + public static System.Threading.Tasks.Task Publish(this NServiceBus.IMessageSession session, object message) { } + public static System.Threading.Tasks.Task Publish(this NServiceBus.IMessageSession session) { } + public static System.Threading.Tasks.Task Publish(this NServiceBus.IMessageSession session, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IMessageSession session, object message) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IMessageSession session, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IMessageSession session, string destination, object message) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IMessageSession session, string destination, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task SendLocal(this NServiceBus.IMessageSession session, object message) { } + public static System.Threading.Tasks.Task SendLocal(this NServiceBus.IMessageSession session, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task Subscribe(this NServiceBus.IMessageSession session, System.Type messageType) { } + public static System.Threading.Tasks.Task Subscribe(this NServiceBus.IMessageSession session) { } + public static System.Threading.Tasks.Task Unsubscribe(this NServiceBus.IMessageSession session, System.Type messageType) { } + public static System.Threading.Tasks.Task Unsubscribe(this NServiceBus.IMessageSession session) { } + } + public class ImmediateConfig + { + public ImmediateConfig(int maxNumberOfRetries) { } + public int MaxNumberOfRetries { get; } + } + public class static ImmediateDispatchOptionExtensions + { + public static bool RequiredImmediateDispatch(this NServiceBus.Extensibility.ExtendableOptions options) { } + public static void RequireImmediateDispatch(this NServiceBus.Extensibility.ExtendableOptions options) { } + } + public class ImmediateRetriesSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public void NumberOfRetries(int numberOfRetries) { } + } + public sealed class ImmediateRetry : NServiceBus.RecoverabilityAction { } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface INeedInitialization + { + void Customize(NServiceBus.EndpointConfiguration configuration); + } + public class InMemoryPersistence : NServiceBus.Persistence.PersistenceDefinition { } + public class static InstallConfigExtensions + { + public static void EnableInstallers(this NServiceBus.EndpointConfiguration config, string username = null) { } + } + public class InstanceMappingFileSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public InstanceMappingFileSettings(NServiceBus.Settings.SettingsHolder settings) { } + public NServiceBus.InstanceMappingFileSettings FilePath(string filePath) { } + public NServiceBus.InstanceMappingFileSettings RefreshInterval(System.TimeSpan refreshInterval) { } + } + public interface IPipelineContext : NServiceBus.Extensibility.IExtendable + { + System.Threading.Tasks.Task Publish(object message, NServiceBus.PublishOptions options); + System.Threading.Tasks.Task Publish(System.Action messageConstructor, NServiceBus.PublishOptions publishOptions); + System.Threading.Tasks.Task Send(object message, NServiceBus.SendOptions options); + System.Threading.Tasks.Task Send(System.Action messageConstructor, NServiceBus.SendOptions options); + } + public class static IPipelineContextExtensions + { + public static System.Threading.Tasks.Task Publish(this NServiceBus.IPipelineContext context, object message) { } + public static System.Threading.Tasks.Task Publish(this NServiceBus.IPipelineContext context) { } + public static System.Threading.Tasks.Task Publish(this NServiceBus.IPipelineContext context, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IPipelineContext context, object message) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IPipelineContext context, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IPipelineContext context, string destination, object message) { } + public static System.Threading.Tasks.Task Send(this NServiceBus.IPipelineContext context, string destination, System.Action messageConstructor) { } + public static System.Threading.Tasks.Task SendLocal(this NServiceBus.IPipelineContext context, object message) { } + public static System.Threading.Tasks.Task SendLocal(this NServiceBus.IPipelineContext context, System.Action messageConstructor) { } + } + [System.ObsoleteAttribute("Use IEndpointInstance to create sending session. Will be removed in version 7.0.0" + + ".", true)] + public interface ISendOnlyBus : System.IDisposable { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExecuteTheseHandlersFirst` instead. Will be removed in" + + " version 7.0.0.", true)] + public interface ISpecifyMessageHandlerOrdering { } + [System.ObsoleteAttribute("Use `IStartableEndpoint` instead. Will be removed in version 7.0.0.", true)] + public interface IStartableBus : NServiceBus.IBus + { + [System.ObsoleteAttribute("Use `IStartableEndpoint` instead. Will be removed in version 7.0.0.", true)] + NServiceBus.IBus Start(); + } + public interface IStartableEndpoint + { + System.Threading.Tasks.Task Start(); + } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IWantToRunBeforeConfigurationIsFinalized + { + void Run(NServiceBus.Settings.SettingsHolder settings); + } + [System.ObsoleteAttribute(@"`IWantToRunWhenBusStartsAndStops` has been moved to the host implementations and renamed. If you're self-hosting, instead of using this interface, you can call any startup code right before `Endpoint.Create` or any cleanup code right after `Endpoint.Stop`. When using either NServiceBus.Host or NServiceBus.Host.AzureCloudService, use the host's interface `IWantToRunWhenEndpointStartsAndStops` instead. Will be removed in version 7.0.0.", true)] + public interface IWantToRunWhenBusStartsAndStops { } + public class JsonSerializer : NServiceBus.Serialization.SerializationDefinition + { + public JsonSerializer() { } + public override System.Func Configure(NServiceBus.Settings.ReadOnlySettings settings) { } + } + public class static JsonSerializerConfigurationExtensions + { + public static void Encoding(this NServiceBus.Serialization.SerializationExtensions config, System.Text.Encoding encoding) { } + [System.ObsoleteAttribute("Use `Encoding(this SerializationExtensions config, Encoding encod" + + "ing)` instead. The member currently throws a NotImplementedException. Will be re" + + "moved in version 7.0.0.", true)] + public static void Encoding(this NServiceBus.SerializationExtentions config, System.Text.Encoding encoding) { } + } + public class static LoadMessageHandlersExtensions + { + public static void ExecuteTheseHandlersFirst(this NServiceBus.EndpointConfiguration config, System.Collections.Generic.IEnumerable handlerTypes) { } + public static void ExecuteTheseHandlersFirst(this NServiceBus.EndpointConfiguration config, params System.Type[] handlerTypes) { } + [System.ObsoleteAttribute("Use `ExecuteTheseHandlersFirst` instead. The member currently throws a NotImpleme" + + "ntedException. Will be removed in version 7.0.0.", true)] + public static void LoadMessageHandlers(this NServiceBus.EndpointConfiguration config) { } + [System.ObsoleteAttribute("Use `ExecuteTheseHandlersFirst` instead. The member currently throws a NotImpleme" + + "ntedException. Will be removed in version 7.0.0.", true)] + public static void LoadMessageHandlers(this NServiceBus.EndpointConfiguration config, NServiceBus.First order) { } + } + [System.ObsoleteAttribute("Use `LoadMessageHandlersExtensions` instead. Will be removed in version 7.0.0.", true)] + public class static LoadMessageHandlersExtentions { } + public struct LogicalAddress + { + public NServiceBus.Routing.EndpointInstance EndpointInstance { get; } + public string Qualifier { get; } + public NServiceBus.LogicalAddress CreateIndividualizedAddress(string discriminator) { } + public static NServiceBus.LogicalAddress CreateLocalAddress(string queueName, System.Collections.Generic.IReadOnlyDictionary properties) { } + public NServiceBus.LogicalAddress CreateQualifiedAddress(string qualifier) { } + public static NServiceBus.LogicalAddress CreateRemoteAddress(NServiceBus.Routing.EndpointInstance endpointInstance) { } + public override bool Equals(object obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public class MessageDeserializationException : System.Runtime.Serialization.SerializationException + { + public MessageDeserializationException(string message) { } + public MessageDeserializationException(string transportMessageId, System.Exception innerException) { } + protected MessageDeserializationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + } + public class static MessageDrivenSubscriptionsConfigExtensions + { + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Type eventType, string publisherEndpoint) + where T : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport { } + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string publisherEndpoint) + where T : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport { } + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string @namespace, string publisherEndpoint) + where T : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport { } + public static void SubscriptionAuthorizer(this NServiceBus.TransportExtensions transportExtensions, System.Func authorizer) + where T : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport { } + } + public class static MessageIdExtensions + { + public static string GetMessageId(this NServiceBus.Extensibility.ExtendableOptions options) { } + public static void SetMessageId(this NServiceBus.Extensibility.ExtendableOptions options, string messageId) { } + } + public enum MessageIntentEnum + { + Send = 1, + Publish = 2, + Subscribe = 3, + Unsubscribe = 4, + Reply = 5, + } + public class static MessageProcessingOptimizationExtensions + { + public static void LimitMessageProcessingConcurrencyTo(this NServiceBus.EndpointConfiguration config, int maxConcurrency) { } + } + public sealed class MoveToError : NServiceBus.RecoverabilityAction + { + public string ErrorQueue { get; } + } + public class static MsmqConfigurationExtensions + { + public static NServiceBus.TransportExtensions ApplyLabelToMessages(this NServiceBus.TransportExtensions transportExtensions, System.Func, string> labelGenerator) { } + public static NServiceBus.InstanceMappingFileSettings InstanceMappingFile(this NServiceBus.RoutingSettings config) { } + public static void SetMessageDistributionStrategy(this NServiceBus.RoutingSettings config, NServiceBus.Routing.DistributionStrategy distributionStrategy) { } + public static NServiceBus.TransportExtensions TransactionScopeOptions(this NServiceBus.TransportExtensions transportExtensions, System.Nullable timeout = null, System.Nullable isolationLevel = null) { } + } + public class MsmqTransport : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport + { + public MsmqTransport() { } + public override string ExampleConnectionStringForErrorMessage { get; } + public override bool RequiresConnectionString { get; } + public override NServiceBus.Transport.TransportInfrastructure Initialize(NServiceBus.Settings.SettingsHolder settings, string connectionString) { } + } + public class NonDurableDelivery : NServiceBus.DeliveryConstraints.DeliveryConstraint + { + public NonDurableDelivery() { } + } + public class Notifications + { + public Notifications() { } + public NServiceBus.Faults.ErrorsNotifications Errors { get; } + [System.ObsoleteAttribute("For performance reasons it is no longer possible to instrument the pipeline execu" + + "tion. The member currently throws a NotImplementedException. Will be removed in " + + "version 7.0.0.", true)] + public NServiceBus.PipelineNotifications Pipeline { get; } + } + [System.ObsoleteAttribute("Use `EndpointConfiguration.ExecuteTheseHandlersFirst` instead. Will be removed in" + + " version 7.0.0.", true)] + public class Order + { + public Order() { } + } + public class static OutboxConfigExtensions + { + public static NServiceBus.Outbox.OutboxSettings EnableOutbox(this NServiceBus.EndpointConfiguration config) { } + } + public class PendingTransportOperations + { + public PendingTransportOperations() { } + public bool HasOperations { get; } + public NServiceBus.Transport.TransportOperation[] Operations { get; } + public void Add(NServiceBus.Transport.TransportOperation transportOperation) { } + public void AddRange(NServiceBus.Transport.TransportOperation[] transportOperations) { } + } + public class static PersistenceConfig + { + public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Persistence.PersistenceDefinition { } + public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Persistence.PersistenceDefinition + where S : NServiceBus.Persistence.StorageType { } + public static NServiceBus.PersistenceExtensions UsePersistence(this NServiceBus.EndpointConfiguration config, System.Type definitionType) { } + } + public class PersistenceExtensions : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public PersistenceExtensions(System.Type definitionType, NServiceBus.Settings.SettingsHolder settings, System.Type storageType) { } + [System.ObsoleteAttribute(@"Example: config.UsePersistence().For(TimeoutStorage) should be changed to config.UsePersistence(). Use `UsePersistence() where T : PersistenceExtension where S : StorageType` instead. Will be removed in version 7.0.0.", true)] + public NServiceBus.PersistenceExtensions For(params NServiceBus.Persistence.Storage[] specificStorages) { } + } + public class PersistenceExtensions : NServiceBus.PersistenceExtensions + where T : NServiceBus.Persistence.PersistenceDefinition + { + public PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings) { } + protected PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings, System.Type storageType) { } + [System.ObsoleteAttribute("Example: config.UsePersistence().For(TimeoutStorage) should " + + "be changed to config.UsePersistence(). Use `UsePe" + + "rsistence()` instead. Will be removed in version 7.0.0.", true)] + public NServiceBus.PersistenceExtensions For(params NServiceBus.Persistence.Storage[] specificStorages) { } + } + public class PersistenceExtensions : NServiceBus.PersistenceExtensions + where T : NServiceBus.Persistence.PersistenceDefinition + where S : NServiceBus.Persistence.StorageType + { + public PersistenceExtensions(NServiceBus.Settings.SettingsHolder settings) { } + } + [System.ObsoleteAttribute("Use `PersistenceExtensions` instead. Will be removed in version 7.0.0.", true)] + public class PersistenceExtentions + { + public PersistenceExtentions() { } + } + [System.ObsoleteAttribute("Use `PersistenceExtensions` instead. Will be removed in version 7.0.0.", true)] + public class PersistenceExtentions + { + public PersistenceExtentions() { } + } + [System.ObsoleteAttribute("Use `PersistenceExtensions` instead. Will be removed in version 7.0.0.", true)] + public class PersistenceExtentions + { + public PersistenceExtentions() { } + } + [System.ObsoleteAttribute("For performance reasons it is no longer possible to instrument the pipeline execu" + + "tion. Will be removed in version 7.0.0.", true)] + public class PipelineNotifications + { + public PipelineNotifications() { } + } + public class PublishOptions : NServiceBus.Extensibility.ExtendableOptions + { + public PublishOptions() { } + } + public class static ReceiveSettingsExtensions + { + public static void MakeInstanceUniquelyAddressable(this NServiceBus.EndpointConfiguration config, string discriminator) { } + public static void OverrideLocalAddress(this NServiceBus.EndpointConfiguration config, string baseInputQueueName) { } + } + public abstract class RecoverabilityAction + { + protected internal RecoverabilityAction() { } + public static NServiceBus.DelayedRetry DelayedRetry(System.TimeSpan timeSpan) { } + public static NServiceBus.ImmediateRetry ImmediateRetry() { } + public static NServiceBus.MoveToError MoveToError(string errorQueue) { } + } + public class RecoverabilityConfig + { + public RecoverabilityConfig(NServiceBus.ImmediateConfig immediateConfig, NServiceBus.DelayedConfig delayedConfig, NServiceBus.FailedConfig failedConfig) { } + public NServiceBus.DelayedConfig Delayed { get; } + public NServiceBus.FailedConfig Failed { get; } + public NServiceBus.ImmediateConfig Immediate { get; } + } + public class static RecoverabilityEndpointConfigurationExtensions + { + public static NServiceBus.RecoverabilitySettings Recoverability(this NServiceBus.EndpointConfiguration configuration) { } + } + public class RecoverabilitySettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public NServiceBus.RecoverabilitySettings CustomPolicy(System.Func custom) { } + public NServiceBus.RecoverabilitySettings Delayed(System.Action customizations) { } + [System.ObsoleteAttribute("Legacy retries satellite is no longer needed as of Version 7. Will be treated as " + + "an error from version 7.0.0. Will be removed in version 8.0.0.", false)] + public NServiceBus.RecoverabilitySettings DisableLegacyRetriesSatellite() { } + public NServiceBus.RecoverabilitySettings Failed(System.Action customizations) { } + public NServiceBus.RecoverabilitySettings Immediate(System.Action customizations) { } + } + public class ReplyOptions : NServiceBus.Extensibility.ExtendableOptions + { + public ReplyOptions() { } + } + public class RetryFailedSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public NServiceBus.RetryFailedSettings HeaderCustomization(System.Action> customization) { } + } + public class static RoutingFeatureSettingsExtensions + { + public static void OverridePublicReturnAddress(this NServiceBus.EndpointConfiguration configuration, string address) { } + } + public class static RoutingOptionExtensions + { + public static string GetDestination(this NServiceBus.ReplyOptions options) { } + public static string GetDestination(this NServiceBus.SendOptions options) { } + public static string GetReplyToRoute(this NServiceBus.ReplyOptions options) { } + public static string GetReplyToRoute(this NServiceBus.SendOptions options) { } + public static string GetRouteToSpecificInstance(this NServiceBus.SendOptions options) { } + public static bool IsRoutingReplyToAnyInstance(this NServiceBus.SendOptions options) { } + public static bool IsRoutingReplyToAnyInstance(this NServiceBus.ReplyOptions options) { } + public static bool IsRoutingReplyToThisInstance(this NServiceBus.SendOptions options) { } + public static bool IsRoutingReplyToThisInstance(this NServiceBus.ReplyOptions options) { } + public static bool IsRoutingToThisEndpoint(this NServiceBus.SendOptions options) { } + public static bool IsRoutingToThisInstance(this NServiceBus.SendOptions options) { } + public static void RouteReplyTo(this NServiceBus.ReplyOptions options, string address) { } + public static void RouteReplyTo(this NServiceBus.SendOptions options, string address) { } + public static void RouteReplyToAnyInstance(this NServiceBus.SendOptions options) { } + public static void RouteReplyToAnyInstance(this NServiceBus.ReplyOptions options) { } + public static void RouteReplyToThisInstance(this NServiceBus.SendOptions options) { } + public static void RouteReplyToThisInstance(this NServiceBus.ReplyOptions options) { } + public static void RouteToSpecificInstance(this NServiceBus.SendOptions options, string instanceId) { } + public static void RouteToThisEndpoint(this NServiceBus.SendOptions options) { } + public static void RouteToThisInstance(this NServiceBus.SendOptions options) { } + public static void SetDestination(this NServiceBus.SendOptions options, string destination) { } + public static void SetDestination(this NServiceBus.ReplyOptions options, string destination) { } + } + public class RoutingSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public RoutingSettings(NServiceBus.Settings.SettingsHolder settings) { } + public void DoNotEnforceBestPractices() { } + public void RouteToEndpoint(System.Type messageType, string destination) { } + public void RouteToEndpoint(System.Reflection.Assembly assembly, string destination) { } + public void RouteToEndpoint(System.Reflection.Assembly assembly, string @namespace, string destination) { } + } + public class RoutingSettings : NServiceBus.RoutingSettings + where T : NServiceBus.Transport.TransportDefinition + { + public RoutingSettings(NServiceBus.Settings.SettingsHolder settings) { } + } + public class static RoutingSettingsExtensions + { + public static NServiceBus.RoutingSettings Routing(this NServiceBus.TransportExtensions config) { } + public static NServiceBus.RoutingSettings Routing(this NServiceBus.TransportExtensions config) + where T : NServiceBus.Transport.TransportDefinition { } + } + public abstract class Saga + { + protected Saga() { } + [System.ObsoleteAttribute("Sagas no longer provide access to bus operations via the .Bus property. Use the c" + + "ontext parameter on the Handle method. The member currently throws a NotImplemen" + + "tedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.IBus Bus { get; set; } + public bool Completed { get; } + public NServiceBus.IContainSagaData Entity { get; set; } + protected internal abstract void ConfigureHowToFindSaga(NServiceBus.IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration); + protected void MarkAsComplete() { } + protected System.Threading.Tasks.Task ReplyToOriginator(NServiceBus.IMessageHandlerContext context, object message) { } + [System.ObsoleteAttribute("Use `ReplyToOriginator(IMessageHandlerContext, object)` instead. The member curre" + + "ntly throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + protected void ReplyToOriginator(object message) { } + [System.ObsoleteAttribute("Construct the message and pass it to the non Action overload. Use `ReplyToOrigina" + + "tor(IMessageHandlerContext, object)` instead. The member currently throws a NotI" + + "mplementedException. Will be removed in version 7.0.0.", true)] + protected virtual void ReplyToOriginator(System.Action messageConstructor) { } + protected System.Threading.Tasks.Task RequestTimeout(NServiceBus.IMessageHandlerContext context, System.DateTime at) + where TTimeoutMessageType : new() { } + protected System.Threading.Tasks.Task RequestTimeout(NServiceBus.IMessageHandlerContext context, System.DateTime at, TTimeoutMessageType timeoutMessage) { } + protected System.Threading.Tasks.Task RequestTimeout(NServiceBus.IMessageHandlerContext context, System.TimeSpan within) + where TTimeoutMessageType : new() { } + protected System.Threading.Tasks.Task RequestTimeout(NServiceBus.IMessageHandlerContext context, System.TimeSpan within, TTimeoutMessageType timeoutMessage) { } + [System.ObsoleteAttribute("Use `RequestTimeout(IMessageHandlerContext, DateTime)` inste" + + "ad. The member currently throws a NotImplementedException. Will be removed in ve" + + "rsion 7.0.0.", true)] + protected void RequestTimeout(System.DateTime at) { } + [System.ObsoleteAttribute("Construct the message and pass it to the non Action overload. Use `RequestTimeout" + + "(IMessageHandlerContext DateTime, TTimeoutMessageType)` ins" + + "tead. The member currently throws a NotImplementedException. Will be removed in " + + "version 7.0.0.", true)] + protected void RequestTimeout(System.DateTime at, System.Action action) { } + [System.ObsoleteAttribute("Use `RequestTimeout(IMessageHandlerContext, DateTime, TTimeo" + + "utMessageType)` instead. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + protected void RequestTimeout(System.DateTime at, TTimeoutMessageType timeoutMessage) { } + [System.ObsoleteAttribute("Use `RequestTimeout(IMessageHandlerContext, TimeSpan)` inste" + + "ad. The member currently throws a NotImplementedException. Will be removed in ve" + + "rsion 7.0.0.", true)] + protected void RequestTimeout(System.TimeSpan within) { } + [System.ObsoleteAttribute(@"Construct the message and pass it to the non Action overload. Use `Saga.RequestTimeout(IMessageHandlerContext, TimeSpan, TTimeoutMessageType)` instead. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + protected void RequestTimeout(System.TimeSpan within, System.Action messageConstructor) { } + [System.ObsoleteAttribute("Use `RequestTimeout(IMessageHandlerContext, TimeSpan, TTimeo" + + "utMessageType)` instead. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + protected void RequestTimeout(System.TimeSpan within, TTimeoutMessageType timeoutMessage) { } + } + public abstract class Saga : NServiceBus.Saga + where TSagaData : NServiceBus.IContainSagaData, new () + { + protected Saga() { } + public TSagaData Data { get; set; } + protected internal override void ConfigureHowToFindSaga(NServiceBus.IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) { } + protected abstract void ConfigureHowToFindSaga(NServiceBus.SagaPropertyMapper mapper); + } + public class SagaPropertyMapper + where TSagaData : NServiceBus.IContainSagaData + { + public NServiceBus.ToSagaExpression ConfigureMapping(System.Linq.Expressions.Expression> messageProperty) { } + } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public class static ScaleOutExtentions + { + [System.ObsoleteAttribute("The member currently throws a NotImplementedException. Will be removed in version" + + " 7.0.0.", true)] + public static NServiceBus.Settings.ScaleOutSettings ScaleOut(this NServiceBus.EndpointConfiguration config) { } + } + [System.ObsoleteAttribute("Use extension methods provided on ISendOnlyBus. Will be removed in version 7.0.0." + + "", true)] + public class Schedule + { + public Schedule() { } + } + public class static ScheduleExtensions + { + [System.ObsoleteAttribute("Use `ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, Func task)` instead. The member currently throws a NotImplementedExce" + + "ption. Will be removed in version 7.0.0.", true)] + public static void ScheduleEvery(this NServiceBus.IMessageSession session, System.TimeSpan timeSpan, System.Action task) { } + [System.ObsoleteAttribute("Use `ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, string name, " + + "Func task)` instead. The member currently throws a NotIm" + + "plementedException. Will be removed in version 7.0.0.", true)] + public static void ScheduleEvery(this NServiceBus.IMessageSession session, System.TimeSpan timeSpan, string name, System.Action task) { } + public static System.Threading.Tasks.Task ScheduleEvery(this NServiceBus.IMessageSession session, System.TimeSpan timeSpan, System.Func task) { } + public static System.Threading.Tasks.Task ScheduleEvery(this NServiceBus.IMessageSession session, System.TimeSpan timeSpan, string name, System.Func task) { } + } + [System.ObsoleteAttribute("Use `endpointConfiguration.Recoverability().Delayed(delayed => )` instead. Will b" + + "e removed in version 7.0.0.", true)] + public class static SecondLevelRetriesConfigExtensions + { + [System.ObsoleteAttribute("Use `endpointConfiguration.Recoverability().Delayed(delayed => )` instead. The me" + + "mber currently throws a NotImplementedException. Will be removed in version 7.0." + + "0.", true)] + public static NServiceBus.SecondLevelRetriesSettings SecondLevelRetries(this NServiceBus.EndpointConfiguration config) { } + } + [System.ObsoleteAttribute("Use `endpointConfiguration.Recoverability().CustomPolicy(Func @custom)` instead. Will be removed in ver" + + "sion 7.0.0.", true)] + public class SecondLevelRetriesSettings + { + public SecondLevelRetriesSettings() { } + [System.ObsoleteAttribute("Use `endpointConfiguration.Recoverability().CustomPolicy(Func @custom)` instead. The member currently t" + + "hrows a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void CustomRetryPolicy(System.Func customPolicy) { } + } + public class SendOptions : NServiceBus.Extensibility.ExtendableOptions + { + public SendOptions() { } + } + public class static SerializationConfigExtensions + { + public static NServiceBus.Serialization.SerializationExtensions AddDeserializer(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Serialization.SerializationDefinition, new () { } + public static NServiceBus.Serialization.SerializationExtensions AddDeserializer(this NServiceBus.EndpointConfiguration config, T serializationDefinition) + where T : NServiceBus.Serialization.SerializationDefinition { } + public static NServiceBus.Serialization.SerializationExtensions UseSerialization(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.Serialization.SerializationDefinition, new () { } + public static NServiceBus.Serialization.SerializationExtensions UseSerialization(this NServiceBus.EndpointConfiguration config, T serializationDefinition) + where T : NServiceBus.Serialization.SerializationDefinition { } + [System.ObsoleteAttribute("To use a custom serializer derive from SerializationDefinition and provide a fact" + + "ory method for creating the serializer instance. The member currently throws a N" + + "otImplementedException. Will be removed in version 7.0.0.", true)] + public static void UseSerialization(this NServiceBus.EndpointConfiguration config, System.Type serializerType) { } + } + public class static SerializationContextExtensions + { + public static bool ShouldSkipSerialization(this NServiceBus.Pipeline.IOutgoingLogicalMessageContext context) { } + public static void SkipSerialization(this NServiceBus.Pipeline.IOutgoingLogicalMessageContext context) { } + } + [System.ObsoleteAttribute("Use `SerializationExtensions` instead. Will be removed in version 7.0.0.", true)] + public class SerializationExtentions + { + public SerializationExtentions() { } + } + public class static SettingsExtensions + { + public static string EndpointName(this NServiceBus.Settings.ReadOnlySettings settings) { } + public static System.Collections.Generic.IList GetAvailableTypes(this NServiceBus.Settings.ReadOnlySettings settings) { } + public static T GetConfigSection(this NServiceBus.Settings.ReadOnlySettings settings) + where T : class, new () { } + public static string InstanceSpecificQueue(this NServiceBus.Settings.ReadOnlySettings settings) { } + public static string LocalAddress(this NServiceBus.Settings.ReadOnlySettings settings) { } + public static NServiceBus.LogicalAddress LogicalAddress(this NServiceBus.Settings.ReadOnlySettings settings) { } + } + [System.ObsoleteAttribute("Use `SettingsExtensions` instead. Will be removed in version 7.0.0.", true)] + public class static SettingsExtentions { } + public class static SLAMonitoringConfig + { + public static void EnableSLAPerformanceCounter(this NServiceBus.EndpointConfiguration config, System.TimeSpan sla) { } + public static void EnableSLAPerformanceCounter(this NServiceBus.EndpointConfiguration config) { } + } + public class static StaticHeadersConfigExtensions + { + public static void AddHeaderToAllOutgoingMessages(this NServiceBus.EndpointConfiguration config, string key, string value) { } + } + public class SubscribeOptions : NServiceBus.Extensibility.ExtendableOptions + { + public SubscribeOptions() { } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface | System.AttributeTargets.All)] + public sealed class TimeToBeReceivedAttribute : System.Attribute + { + public TimeToBeReceivedAttribute(string timeSpan) { } + public System.TimeSpan TimeToBeReceived { get; } + } + public class static TimeToBeReceivedConventionExtensions + { + public static NServiceBus.ConventionsBuilder DefiningTimeToBeReceivedAs(this NServiceBus.ConventionsBuilder builder, System.Func retrieveTimeToBeReceived) { } + } + public class ToSagaExpression + where TSagaData : NServiceBus.IContainSagaData + { + public ToSagaExpression(NServiceBus.IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration, System.Linq.Expressions.Expression> messageProperty) { } + public void ToSaga(System.Linq.Expressions.Expression> sagaEntityProperty) { } + } + public class TransportExtensions : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public TransportExtensions(NServiceBus.Settings.SettingsHolder settings) { } + public NServiceBus.TransportExtensions ConnectionString(string connectionString) { } + public NServiceBus.TransportExtensions ConnectionString(System.Func connectionString) { } + public NServiceBus.TransportExtensions ConnectionStringName(string name) { } + public NServiceBus.TransportExtensions Transactions(NServiceBus.TransportTransactionMode transportTransactionMode) { } + } + public class TransportExtensions : NServiceBus.TransportExtensions + where T : NServiceBus.Transport.TransportDefinition + { + public TransportExtensions(NServiceBus.Settings.SettingsHolder settings) { } + public NServiceBus.TransportExtensions ConnectionString(string connectionString) { } + public NServiceBus.TransportExtensions ConnectionString(System.Func connectionString) { } + public NServiceBus.TransportExtensions ConnectionStringName(string name) { } + public NServiceBus.TransportExtensions Transactions(NServiceBus.TransportTransactionMode transportTransactionMode) { } + } + [System.ObsoleteAttribute("Not used anymore, use `OutgoingMessage` or `IncomingMessage` instead. Will be rem" + + "oved in version 7.0.0.", true)] + public class TransportMessage + { + public TransportMessage() { } + [System.ObsoleteAttribute("Use the value of the \'IncomingMessage.Body\' or \'OutgoingMessage.Body\' instead. Th" + + "e member currently throws a NotImplementedException. Will be removed in version " + + "7.0.0.", true)] + public byte[] Body { get; set; } + [System.ObsoleteAttribute("Use the value of the \'NServiceBus.CorrelationId\' header instead. The member curre" + + "ntly throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public string CorrelationId { get; set; } + [System.ObsoleteAttribute("Use the value of the \'IncomingMessage.Headers\' or \'OutgoingMesssage.Headers\' inst" + + "ead. Will be removed in version 7.0.0.", true)] + public System.Collections.Generic.Dictionary Headers { get; } + [System.ObsoleteAttribute("Use the value of the \'IncomingMessage.MessageId\' or \'OutgoingMesssage.MessageId\' " + + "instead. The member currently throws a NotImplementedException. Will be removed " + + "in version 7.0.0.", true)] + public string Id { get; } + [System.ObsoleteAttribute("Use `GetMessageIntent(this IncomingMessage message)` instead. The member currentl" + + "y throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.MessageIntentEnum MessageIntent { get; } + [System.ObsoleteAttribute(@"For sending purposes use `DeliveryConstraintContextExtensions.AddDeliveryConstraint(new NonDurableDelivery())` to set NonDurable delivery or `NonDurableDelivery constraint;DeliveryConstraintContextExtensions.TryGetDeliveryConstraint(out constraint)` to read wether NonDurable delivery is set. When receiving look at the new 'NServiceBus.NonDurableMessage' header. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public bool Recoverable { get; set; } + [System.ObsoleteAttribute("Use `GetReplyToAddress(this IncomingMessage message)` instead. The member current" + + "ly throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public string ReplyToAddress { get; } + [System.ObsoleteAttribute(@"For sending purposes use `DeliveryConstraintContextExtensions.AddDeliveryConstraint(new DiscardIfNotReceivedBefore(timeToBeReceived))` to set the `TimeToBeReceived` or `DiscardIfNotReceivedBefore constraint;DeliveryConstraintContextExtensions.TryGetDeliveryConstraint(out constraint)` to read the `TimeToBeReceived`. When receiving look at the new 'NServiceBus.TimeToBeReceived' header. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public System.TimeSpan TimeToBeReceived { get; set; } + } + public enum TransportTransactionMode + { + None = 0, + ReceiveOnly = 1, + SendsAtomicWithReceive = 2, + TransactionScope = 3, + } + public class UnitOfWorkSettings + { + public NServiceBus.UnitOfWorkSettings WrapHandlersInATransactionScope(System.Nullable timeout = null, System.Nullable isolationLevel = null) { } + } + public class static UnitOfWorkSettingsExtensions + { + public static NServiceBus.UnitOfWorkSettings UnitOfWork(this NServiceBus.EndpointConfiguration config) { } + } + public class UnsubscribeOptions : NServiceBus.Extensibility.ExtendableOptions + { + public UnsubscribeOptions() { } + } + public class static UseDataBusExtensions + { + public static NServiceBus.DataBus.DataBusExtensions UseDataBus(this NServiceBus.EndpointConfiguration config) + where T : NServiceBus.DataBus.DataBusDefinition, new () { } + public static NServiceBus.DataBus.DataBusExtensions UseDataBus(this NServiceBus.EndpointConfiguration config, System.Type dataBusType) { } + } + public class static UseTransportExtensions + { + public static NServiceBus.TransportExtensions UseTransport(this NServiceBus.EndpointConfiguration endpointConfiguration) + where T : NServiceBus.Transport.TransportDefinition, new () { } + public static NServiceBus.TransportExtensions UseTransport(this NServiceBus.EndpointConfiguration endpointConfiguration, System.Type transportDefinitionType) { } + } + public class WireEncryptedString : System.Runtime.Serialization.ISerializable + { + public WireEncryptedString() { } + public WireEncryptedString(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + [System.ObsoleteAttribute("No longer required. Will be removed in version 7.0.0.", true)] + public string Base64Iv { get; set; } + [System.ObsoleteAttribute("No longer required. Will be removed in version 7.0.0.", true)] + public string EncryptedBase64Value { get; set; } + public NServiceBus.EncryptedValue EncryptedValue { get; set; } + public string Value { get; set; } + public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + } + public class static XmlSerializationExtensions + { + public static NServiceBus.Serialization.SerializationExtensions DontWrapRawXml(this NServiceBus.Serialization.SerializationExtensions config) { } + [System.ObsoleteAttribute("Use `DontWrapRawXml(this SerializationExtensions config)` instead." + + " The member currently throws a NotImplementedException. Will be removed in versi" + + "on 7.0.0.", true)] + public static NServiceBus.SerializationExtentions DontWrapRawXml(this NServiceBus.SerializationExtentions config) { } + public static NServiceBus.Serialization.SerializationExtensions Namespace(this NServiceBus.Serialization.SerializationExtensions config, string namespaceToUse) { } + [System.ObsoleteAttribute("Use `Namespace(this SerializationExtensions config, string namespa" + + "ceToUse)` instead. The member currently throws a NotImplementedException. Will b" + + "e removed in version 7.0.0.", true)] + public static NServiceBus.SerializationExtentions Namespace(this NServiceBus.SerializationExtentions config, string namespaceToUse) { } + public static NServiceBus.Serialization.SerializationExtensions SanitizeInput(this NServiceBus.Serialization.SerializationExtensions config) { } + [System.ObsoleteAttribute("Use `SanitizeInput(this SerializationExtensions config)` instead. " + + "The member currently throws a NotImplementedException. Will be removed in versio" + + "n 7.0.0.", true)] + public static NServiceBus.SerializationExtentions SanitizeInput(this NServiceBus.SerializationExtentions config) { } + } + public class XmlSerializer : NServiceBus.Serialization.SerializationDefinition + { + public XmlSerializer() { } + public override System.Func Configure(NServiceBus.Settings.ReadOnlySettings settings) { } + } +} +namespace NServiceBus.AutomaticSubscriptions.Config +{ + public class AutoSubscribeSettings + { + [System.ObsoleteAttribute("Encourages bad practices. IMessageSession.Subscribe should be explicitly used. Th" + + "e member currently throws a NotImplementedException. Will be removed in version " + + "7.0.0.", true)] + public void AutoSubscribePlainMessages() { } + public void DoNotAutoSubscribeSagas() { } + [System.ObsoleteAttribute("Transports with support for centralized pubsub will default this to true. Can saf" + + "ely be removed. The member currently throws a NotImplementedException. Will be r" + + "emoved in version 7.0.0.", true)] + public void DoNotRequireExplicitRouting() { } + } +} +namespace NServiceBus.Config +{ + public class AuditConfig : System.Configuration.ConfigurationSection + { + public AuditConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("OverrideTimeToBeReceived", IsRequired=false)] + public System.TimeSpan OverrideTimeToBeReceived { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("QueueName", IsRequired=false)] + public string QueueName { get; set; } + } + [System.ObsoleteAttribute("Use the feature concept instead via A class which inherits from `NServiceBus.Feat" + + "ures.Feature` and use `configuration.EnableFeature()`. Will be remove" + + "d in version 7.0.0.", true)] + public interface IWantToRunWhenConfigurationIsComplete { } + public enum KeyFormat + { + Ascii = 0, + Base64 = 1, + } + public class Logging : System.Configuration.ConfigurationSection + { + public Logging() { } + [System.Configuration.ConfigurationPropertyAttribute("Threshold", DefaultValue="Info", IsRequired=true)] + public string Threshold { get; set; } + } + [System.ObsoleteAttribute("Use `EndpointConfiguration.EnlistWithLegacyMSMQDistributor` instead. Will be remo" + + "ved in version 7.0.0.", true)] + public class MasterNodeConfig : System.Configuration.ConfigurationSection + { + public MasterNodeConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("Node", IsRequired=false)] + [System.ObsoleteAttribute("Use `EndpointConfiguration.EnlistWithLegacyMSMQDistributor` instead. Will be remo" + + "ved in version 7.0.0.", true)] + public string Node { get; set; } + } + public class MessageEndpointMapping : System.Configuration.ConfigurationElement, System.IComparable + { + public MessageEndpointMapping() { } + [System.Configuration.ConfigurationPropertyAttribute("Assembly", IsRequired=false)] + public string AssemblyName { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("Endpoint", IsRequired=true)] + public string Endpoint { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("Messages", IsRequired=false)] + public string Messages { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("Namespace", IsRequired=false)] + public string Namespace { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("Type", IsRequired=false)] + public string TypeFullName { get; set; } + public int CompareTo(NServiceBus.Config.MessageEndpointMapping other) { } + public void Configure(System.Action mapTypeToEndpoint) { } + } + public class MessageEndpointMappingCollection : System.Configuration.ConfigurationElementCollection + { + public MessageEndpointMappingCollection() { } + public new string AddElementName { get; set; } + public new string ClearElementName { get; set; } + public override System.Configuration.ConfigurationElementCollectionType CollectionType { get; } + public new int Count { get; } + public NServiceBus.Config.MessageEndpointMapping this[int index] { get; set; } + public NServiceBus.Config.MessageEndpointMapping this[string Name] { get; } + public new string RemoveElementName { get; } + public void Add(NServiceBus.Config.MessageEndpointMapping mapping) { } + protected override void BaseAdd(System.Configuration.ConfigurationElement element) { } + public void Clear() { } + protected override System.Configuration.ConfigurationElement CreateNewElement() { } + protected override System.Configuration.ConfigurationElement CreateNewElement(string elementName) { } + protected override object GetElementKey(System.Configuration.ConfigurationElement element) { } + public int IndexOf(NServiceBus.Config.MessageEndpointMapping mapping) { } + public override bool IsReadOnly() { } + public void Remove(NServiceBus.Config.MessageEndpointMapping mapping) { } + public void Remove(string name) { } + public void RemoveAt(int index) { } + } + public class MessageForwardingInCaseOfFaultConfig : System.Configuration.ConfigurationSection + { + public MessageForwardingInCaseOfFaultConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("ErrorQueue", IsRequired=true)] + public string ErrorQueue { get; set; } + } + public class MsmqSubscriptionStorageConfig : System.Configuration.ConfigurationSection + { + public MsmqSubscriptionStorageConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("Queue", IsRequired=true)] + public string Queue { get; set; } + } + public class RijndaelEncryptionServiceConfig : System.Configuration.ConfigurationSection + { + public RijndaelEncryptionServiceConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("ExpiredKeys", IsRequired=false)] + public NServiceBus.Config.RijndaelExpiredKeyCollection ExpiredKeys { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("Key", IsRequired=true)] + public string Key { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("KeyFormat", IsRequired=false)] + public NServiceBus.Config.KeyFormat KeyFormat { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("KeyIdentifier", IsRequired=false)] + public string KeyIdentifier { get; set; } + } + public class RijndaelExpiredKey : System.Configuration.ConfigurationElement + { + public RijndaelExpiredKey() { } + [System.Configuration.ConfigurationPropertyAttribute("Key", IsRequired=true)] + public string Key { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("KeyFormat", IsRequired=false)] + public NServiceBus.Config.KeyFormat KeyFormat { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("KeyIdentifier", IsRequired=false)] + public string KeyIdentifier { get; set; } + } + public class RijndaelExpiredKeyCollection : System.Configuration.ConfigurationElementCollection + { + public RijndaelExpiredKeyCollection() { } + public override System.Configuration.ConfigurationElementCollectionType CollectionType { get; } + public NServiceBus.Config.RijndaelExpiredKey this[int index] { get; set; } + public NServiceBus.Config.RijndaelExpiredKey this[string key] { get; } + public void Add(NServiceBus.Config.RijndaelExpiredKey mapping) { } + protected override void BaseAdd(System.Configuration.ConfigurationElement element) { } + public void Clear() { } + protected override System.Configuration.ConfigurationElement CreateNewElement() { } + protected override System.Configuration.ConfigurationElement CreateNewElement(string elementName) { } + protected override object GetElementKey(System.Configuration.ConfigurationElement element) { } + public int IndexOf(NServiceBus.Config.RijndaelExpiredKey encryptionKey) { } + public override bool IsReadOnly() { } + public void Remove(NServiceBus.Config.RijndaelExpiredKey mapping) { } + public void Remove(string name) { } + public void RemoveAt(int index) { } + } + [System.ObsoleteAttribute("Second Level Retries has been renamed to Delayed Retries. The app.config API has " + + "been removed, use the code API via endpointConfiguration.Recoverability().Delaye" + + "d(settings => ...);. Will be removed in version 7.0.0.", true)] + public class SecondLevelRetriesConfig : System.Configuration.ConfigurationSection + { + public SecondLevelRetriesConfig() { } + [System.ObsoleteAttribute(@"Second Level Retries has been renamed to Delayed Retries. The app.config API has been removed, use the code API via endpointConfiguration.Recoverability().Delayed(settings => ...);. To disable use endpointConfiguration.Recoverability().Delayed(settings => settings.NumberOfRetries(0));. Will be removed in version 7.0.0.", true)] + public bool Enabled { get; set; } + [System.ObsoleteAttribute(@"Second Level Retries has been renamed to Delayed Retries. The app.config API has been removed, use the code API via endpointConfiguration.Recoverability().Delayed(settings => ...);. To change the NumberOfRetries use endpointConfiguration.Recoverability().Delayed(settings => settings.NumberOfRetries(5);. Will be removed in version 7.0.0.", true)] + public int NumberOfRetries { get; set; } + [System.ObsoleteAttribute(@"Second Level Retries has been renamed to Delayed Retries. The app.config API has been removed, use the code API via endpointConfiguration.Recoverability().Delayed(settings => ...);. To change the TimeIncrease use endpointConfiguration.Recoverability().Delayed(settings => settings.TimeIncrease(TimeSpan.FromMinutes(5));. Will be removed in version 7.0.0.", true)] + public System.TimeSpan TimeIncrease { get; set; } + } + [System.ObsoleteAttribute("The app.config API TransportConfig has been removed, use the code API. Will be re" + + "moved in version 7.0.0.", true)] + public class TransportConfig : System.Configuration.ConfigurationSection + { + public TransportConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("MaximumConcurrencyLevel", DefaultValue=0, IsRequired=false)] + [System.ObsoleteAttribute("The app.config API TransportConfig has been removed, use the code API. To change " + + "the concurrency level use endpointConfiguration.LimitMessageProcessingConcurrenc" + + "yTo(1);. Will be removed in version 7.0.0.", true)] + public int MaximumConcurrencyLevel { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("MaximumMessageThroughputPerSecond", DefaultValue=-1, IsRequired=false)] + [System.ObsoleteAttribute("Message throughput throttling has been removed. Consult the documentation for fur" + + "ther information. Will be removed in version 7.0.0.", true)] + public int MaximumMessageThroughputPerSecond { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("MaxRetries", DefaultValue=5, IsRequired=false)] + [System.ObsoleteAttribute("The app.config API TransportConfig has been removed, use the code API. To change " + + "the NumberOfRetries use endpointConfiguration.Recoverability().Immediate(setting" + + "s => settings.NumberOfRetries(5);. Will be removed in version 7.0.0.", true)] + public int MaxRetries { get; set; } + } + public class UnicastBusConfig : System.Configuration.ConfigurationSection + { + public UnicastBusConfig() { } + [System.Configuration.ConfigurationPropertyAttribute("DistributorControlAddress", IsRequired=false)] + [System.ObsoleteAttribute("Switch to the code API by using \'EndpointConfiguration.EnlistWithLegacyMSMQDistri" + + "butor\' instead. Will be removed in version 7.0.0.", true)] + public string DistributorControlAddress { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("DistributorDataAddress", IsRequired=false)] + [System.ObsoleteAttribute("Switch to the code API by using \'EndpointConfiguration.EnlistWithLegacyMSMQDistri" + + "butor\' instead. Will be removed in version 7.0.0.", true)] + public string DistributorDataAddress { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("ForwardReceivedMessagesTo", IsRequired=false)] + [System.ObsoleteAttribute("Use \'EndpointConfiguration.ForwardReceivedMessagesTo\' to configure the forwarding" + + " address. Will be removed in version 7.0.0.", true)] + public string ForwardReceivedMessagesTo { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("MessageEndpointMappings", IsRequired=false)] + public NServiceBus.Config.MessageEndpointMappingCollection MessageEndpointMappings { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("TimeoutManagerAddress", IsRequired=false)] + public string TimeoutManagerAddress { get; set; } + [System.Configuration.ConfigurationPropertyAttribute("TimeToBeReceivedOnForwardedMessages", IsRequired=false)] + public System.TimeSpan TimeToBeReceivedOnForwardedMessages { get; set; } + } +} +namespace NServiceBus.Config.ConfigurationSource +{ + public class DefaultConfigurationSource : NServiceBus.Config.ConfigurationSource.IConfigurationSource + { + public DefaultConfigurationSource() { } + } + public interface IConfigurationSource + { + T GetConfiguration() + where T : class, new (); + } + public interface IProvideConfiguration + { + T GetConfiguration(); + } +} +namespace NServiceBus.Configuration.AdvanceExtensibility +{ + public class static AdvanceExtensibilityExtensions + { + public static NServiceBus.Settings.SettingsHolder GetSettings(this NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings config) { } + } + public abstract class ExposeSettings + { + protected ExposeSettings(NServiceBus.Settings.SettingsHolder settings) { } + } +} +namespace NServiceBus.ConsistencyGuarantees +{ + public class static TransactionModeSettingsExtensions + { + public static NServiceBus.TransportTransactionMode GetRequiredTransactionModeForReceives(this NServiceBus.Settings.ReadOnlySettings settings) { } + } +} +namespace NServiceBus.Container +{ + public class ContainerCustomizations + { + public NServiceBus.Settings.SettingsHolder Settings { get; } + } + public abstract class ContainerDefinition + { + protected ContainerDefinition() { } + public abstract NServiceBus.ObjectBuilder.Common.IContainer CreateContainer(NServiceBus.Settings.ReadOnlySettings settings); + } +} +namespace NServiceBus.DataBus +{ + public abstract class DataBusDefinition + { + protected DataBusDefinition() { } + protected internal abstract System.Type ProvidedByFeature(); + } + public class DataBusExtensions : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + public DataBusExtensions(NServiceBus.Settings.SettingsHolder settings) { } + } + public class DataBusExtensions : NServiceBus.DataBus.DataBusExtensions + where T : NServiceBus.DataBus.DataBusDefinition + { + public DataBusExtensions(NServiceBus.Settings.SettingsHolder settings) { } + } + [System.ObsoleteAttribute("Use `DataBusExtensions` instead. Will be removed in version 7.0.0.", true)] + public class DataBusExtentions + { + public DataBusExtentions() { } + } + [System.ObsoleteAttribute("Use `DataBusExtensions` instead. Will be removed in version 7.0.0.", true)] + public class DataBusExtentions + { + public DataBusExtentions() { } + } + public interface IDataBus + { + System.Threading.Tasks.Task Get(string key); + System.Threading.Tasks.Task Put(System.IO.Stream stream, System.TimeSpan timeToBeReceived); + System.Threading.Tasks.Task Start(); + } + public interface IDataBusSerializer + { + object Deserialize(System.IO.Stream stream); + void Serialize(object databusProperty, System.IO.Stream stream); + } +} +namespace NServiceBus.DelayedDelivery +{ + public class DelayDeliveryWith : NServiceBus.DelayedDelivery.DelayedDeliveryConstraint + { + public DelayDeliveryWith(System.TimeSpan delay) { } + public System.TimeSpan Delay { get; } + } + public abstract class DelayedDeliveryConstraint : NServiceBus.DeliveryConstraints.DeliveryConstraint + { + protected DelayedDeliveryConstraint() { } + } + public class DoNotDeliverBefore : NServiceBus.DelayedDelivery.DelayedDeliveryConstraint + { + public DoNotDeliverBefore(System.DateTime at) { } + public System.DateTime At { get; } + } +} +namespace NServiceBus.DeliveryConstraints +{ + public abstract class DeliveryConstraint + { + protected DeliveryConstraint() { } + } + public class static DeliveryConstraintContextExtensions + { + public static void AddDeliveryConstraint(this NServiceBus.Extensibility.ContextBag context, NServiceBus.DeliveryConstraints.DeliveryConstraint constraint) { } + public static System.Collections.Generic.List GetDeliveryConstraints(this NServiceBus.Extensibility.ContextBag context) { } + public static void RemoveDeliveryConstaint(this NServiceBus.Extensibility.ContextBag context, NServiceBus.DeliveryConstraints.DeliveryConstraint constraint) { } + public static bool TryGetDeliveryConstraint(this NServiceBus.Extensibility.ContextBag context, out T constraint) + where T : NServiceBus.DeliveryConstraints.DeliveryConstraint { } + public static bool TryRemoveDeliveryConstraint(this NServiceBus.Extensibility.ContextBag context, out T constraint) + where T : NServiceBus.DeliveryConstraints.DeliveryConstraint { } + } +} +namespace NServiceBus.Extensibility +{ + public class ContextBag : NServiceBus.Extensibility.ReadOnlyContextBag + { + public ContextBag(NServiceBus.Extensibility.ContextBag parentBag = null) { } + public T Get() { } + public T GetOrCreate() + where T : class, new () { } + public void Remove() { } + public void Remove(string key) { } + public void Set(T t) { } + public void Set(string key, T t) { } + public bool TryGet(out T result) { } + public bool TryGet(string key, out T result) { } + } + public abstract class ExtendableOptions + { + protected ExtendableOptions() { } + } + public class static ExtendableOptionsExtensions + { + public static NServiceBus.Extensibility.ContextBag GetExtensions(this NServiceBus.Extensibility.ExtendableOptions options) { } + } + public interface IExtendable + { + NServiceBus.Extensibility.ContextBag Extensions { get; } + } + public interface ReadOnlyContextBag + { + T Get(); + bool TryGet(out T result); + bool TryGet(string key, out T result); + } +} +namespace NServiceBus.Faults +{ + public class DelayedRetryMessage + { + public DelayedRetryMessage(System.Collections.Generic.Dictionary headers, byte[] body, System.Exception exception, int retryAttempt) { } + public byte[] Body { get; } + public System.Exception Exception { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public int RetryAttempt { get; } + } + public class ErrorsNotifications + { + [System.ObsoleteAttribute("Use `MessageHasBeenSentToDelayedRetries` instead. Will be removed in version 7.0." + + "0.", true)] + public System.EventHandler MessageHasBeenSentToSecondLevelRetries; + [System.ObsoleteAttribute("Use `MessageHasFailedAnImmediateRetryAttempt` instead. Will be removed in version" + + " 7.0.0.", true)] + public System.EventHandler MessageHasFailedAFirstLevelRetryAttempt; + public ErrorsNotifications() { } + public event System.EventHandler MessageHasBeenSentToDelayedRetries; + public event System.EventHandler MessageHasFailedAnImmediateRetryAttempt; + public event System.EventHandler MessageSentToErrorQueue; + } + public class FailedMessage + { + [System.ObsoleteAttribute("Use `FailedMessage(string messageId, Dictionary headers, byte[] b" + + "ody, Exception exception, string errorQueue)` instead. The member currently thro" + + "ws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public FailedMessage(string messageId, System.Collections.Generic.Dictionary headers, byte[] body, System.Exception exception) { } + public FailedMessage(string messageId, System.Collections.Generic.Dictionary headers, byte[] body, System.Exception exception, string errorQueue) { } + public byte[] Body { get; } + public string ErrorQueue { get; } + public System.Exception Exception { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + } + public class static FaultsHeaderKeys + { + public const string FailedQ = "NServiceBus.FailedQ"; + } + [System.ObsoleteAttribute("First Level Retries has been renamed to Immediate Retries. Use `NServiceBus.Fault" + + "s.ImmediateRetryMessage` instead. Will be removed in version 7.0.0.", true)] + public struct FirstLevelRetry { } + [System.ObsoleteAttribute("IManageMessageFailures is no longer an extension point. To take control of the er" + + "ror handling part of the message processing pipeline, review the Version 5 to 6 " + + "upgrade guide for details. Will be removed in version 7.0.0.", true)] + public interface IManageMessageFailures { } + public class ImmediateRetryMessage + { + public ImmediateRetryMessage(string messageId, System.Collections.Generic.Dictionary headers, byte[] body, System.Exception exception, int retryAttempt) { } + public byte[] Body { get; } + public System.Exception Exception { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + public int RetryAttempt { get; } + } + [System.ObsoleteAttribute("Second Level Retries has been renamed to Delayed Retries. Use `NServiceBus.Faults" + + ".DelayedRetryMessage` instead. Will be removed in version 7.0.0.", true)] + public struct SecondLevelRetry { } +} +namespace NServiceBus.Features +{ + public class Audit : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class AutoSubscribe : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("Use extensions provided by the TransportDefinition class instead. Will be removed" + + " in version 7.0.0.", true)] + public class ConfigureTransport + { + public ConfigureTransport() { } + } + public class CriticalTimeMonitoring : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class DataBus : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("Encryption is no longer enabled by default. Encryption gets enabled by calling co" + + "nfiguration.RegisterEncryptionService or configuration.RijndaelEncryptionService" + + ". Will be removed in version 7.0.0.", true)] + public class Encryptor + { + public Encryptor() { } + } + public abstract class Feature + { + protected Feature() { } + public bool IsActive { get; } + public bool IsEnabledByDefault { get; } + public string Name { get; } + public string Version { get; } + protected void Defaults(System.Action settings) { } + protected void DependsOn() + where T : NServiceBus.Features.Feature { } + protected void DependsOn(string featureTypeName) { } + protected void DependsOnAtLeastOne(params System.Type[] features) { } + protected void DependsOnAtLeastOne(params string[] featureNames) { } + protected void DependsOnOptionally(string featureName) { } + protected void DependsOnOptionally(System.Type featureType) { } + protected void DependsOnOptionally() + where T : NServiceBus.Features.Feature { } + protected void EnableByDefault() { } + protected void Prerequisite(System.Func condition, string description) { } + [System.ObsoleteAttribute("Use `FeatureConfigurationContext.RegisterStartupTask` instead. The member current" + + "ly throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + protected void RegisterStartupTask() + where T : NServiceBus.Features.FeatureStartupTask { } + protected internal abstract void Setup(NServiceBus.Features.FeatureConfigurationContext context); + public override string ToString() { } + } + public class FeatureConfigurationContext + { + public NServiceBus.ObjectBuilder.IConfigureComponents Container { get; } + public NServiceBus.Pipeline.PipelineSettings Pipeline { get; } + public NServiceBus.Settings.ReadOnlySettings Settings { get; } + public void AddSatelliteReceiver(string name, string transportAddress, NServiceBus.TransportTransactionMode requiredTransportTransactionMode, NServiceBus.Transport.PushRuntimeSettings runtimeSettings, System.Func recoverabilityPolicy, System.Func onMessage) { } + public void RegisterStartupTask(TTask startupTask) + where TTask : NServiceBus.Features.FeatureStartupTask { } + public void RegisterStartupTask(System.Func startupTaskFactory) + where TTask : NServiceBus.Features.FeatureStartupTask { } + public void RegisterStartupTask(System.Func startupTaskFactory) + where TTask : NServiceBus.Features.FeatureStartupTask { } + } + public abstract class FeatureStartupTask + { + protected FeatureStartupTask() { } + protected abstract System.Threading.Tasks.Task OnStart(NServiceBus.IMessageSession session); + protected abstract System.Threading.Tasks.Task OnStop(NServiceBus.IMessageSession session); + } + public enum FeatureState + { + Disabled = 0, + Enabled = 1, + Active = 2, + Deactivated = 3, + } + [System.ObsoleteAttribute("FirstLevelRetries is no longer a separate feature. Please use endpointConfigurati" + + "on.Recoverability().Immediate(cfg => cfg.NumberOfRetries(0)); to disable Immedia" + + "te Retries. Will be removed in version 7.0.0.", true)] + public class FirstLevelRetries : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class ForwardReceivedMessages : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class InMemoryGatewayPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class InMemoryOutboxPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class InMemorySagaPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class InMemorySubscriptionPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class InMemoryTimeoutPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class MessageDrivenSubscriptions : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class MsmqSubscriptionPersistence : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class Outbox : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class Sagas : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + public class Scheduler : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("SecondLevelRetries is no longer a separate feature. Please use endpointConfigurat" + + "ion.Recoverability().Delayed(cfg => cfg.NumberOfRetries(0)) to disable Delayed R" + + "etries. Will be removed in version 7.0.0.", true)] + public class SecondLevelRetries : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("Use the ConfigureSerialization Feature class instead. Use `ConfigureSerialization" + + "` instead. Will be removed in version 7.0.0.", true)] + public class static SerializationFeatureHelper { } + public class static SettingsExtensions + { + public static NServiceBus.Settings.SettingsHolder EnableFeatureByDefault(this NServiceBus.Settings.SettingsHolder settings) + where T : NServiceBus.Features.Feature { } + public static NServiceBus.Settings.SettingsHolder EnableFeatureByDefault(this NServiceBus.Settings.SettingsHolder settings, System.Type featureType) { } + public static bool IsFeatureActive(this NServiceBus.Settings.ReadOnlySettings settings, System.Type featureType) { } + public static bool IsFeatureEnabled(this NServiceBus.Settings.ReadOnlySettings settings, System.Type featureType) { } + } + public class SLAMonitoring : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("No longer used, safe to remove. Will be removed in version 7.0.0.", true)] + public class StorageDrivenPublishing + { + public StorageDrivenPublishing() { } + } + public class TimeoutManager : NServiceBus.Features.Feature + { + protected internal override void Setup(NServiceBus.Features.FeatureConfigurationContext context) { } + } + [System.ObsoleteAttribute("No longer used, safe to remove. Will be removed in version 7.0.0.", true)] + public class TimeoutManagerBasedDeferral + { + public TimeoutManagerBasedDeferral() { } + } +} +namespace NServiceBus.Gateway.Deduplication +{ + public interface IDeduplicateMessages + { + System.Threading.Tasks.Task DeduplicateMessage(string clientId, System.DateTime timeReceived, NServiceBus.Extensibility.ContextBag context); + } +} +namespace NServiceBus.Hosting.Helpers +{ + public class AssemblyScanner + { + public AssemblyScanner() { } + public AssemblyScanner(string baseDirectoryToScan) { } + [System.ObsoleteAttribute("This method is no longer required since deep scanning of assemblies is done to de" + + "tect an NServiceBus reference. The member currently throws a NotImplementedExcep" + + "tion. Will be removed in version 7.0.0.", true)] + public System.Collections.Generic.List MustReferenceAtLeastOneAssembly { get; } + public bool ThrowExceptions { get; set; } + public NServiceBus.Hosting.Helpers.AssemblyScannerResults GetScannableAssemblies() { } + } + public class AssemblyScannerResults + { + public AssemblyScannerResults() { } + public System.Collections.Generic.List Assemblies { get; } + public bool ErrorsThrownDuringScanning { get; } + public System.Collections.Generic.List SkippedFiles { get; } + public System.Collections.Generic.List Types { get; } + } + public class SkippedFile + { + public string FilePath { get; } + public string SkipReason { get; } + } +} +namespace NServiceBus.Hosting +{ + public class HostInformation + { + public HostInformation(System.Guid hostId, string displayName) { } + public HostInformation(System.Guid hostId, string displayName, System.Collections.Generic.Dictionary properties) { } + public string DisplayName { get; } + public System.Guid HostId { get; } + public System.Collections.Generic.Dictionary Properties { get; } + } +} +namespace NServiceBus.InMemory.Outbox +{ + public class static InMemoryOutboxSettingsExtensions + { + public static NServiceBus.Outbox.OutboxSettings TimeToKeepDeduplicationData(this NServiceBus.Outbox.OutboxSettings settings, System.TimeSpan time) { } + } +} +namespace NServiceBus.Installation +{ + public interface INeedToInstallSomething + { + System.Threading.Tasks.Task Install(string identity); + } +} +namespace NServiceBus.Logging +{ + public class DefaultFactory : NServiceBus.Logging.LoggingFactoryDefinition + { + public DefaultFactory() { } + public void Directory(string directory) { } + protected internal override NServiceBus.Logging.ILoggerFactory GetLoggingFactory() { } + public void Level(NServiceBus.Logging.LogLevel level) { } + } + public interface ILog + { + bool IsDebugEnabled { get; } + bool IsErrorEnabled { get; } + bool IsFatalEnabled { get; } + bool IsInfoEnabled { get; } + bool IsWarnEnabled { get; } + void Debug(string message); + void Debug(string message, System.Exception exception); + [JetBrains.Annotations.StringFormatMethodAttribute("format")] + void DebugFormat(string format, params object[] args); + void Error(string message); + void Error(string message, System.Exception exception); + [JetBrains.Annotations.StringFormatMethodAttribute("format")] + void ErrorFormat(string format, params object[] args); + void Fatal(string message); + void Fatal(string message, System.Exception exception); + [JetBrains.Annotations.StringFormatMethodAttribute("format")] + void FatalFormat(string format, params object[] args); + void Info(string message); + void Info(string message, System.Exception exception); + [JetBrains.Annotations.StringFormatMethodAttribute("format")] + void InfoFormat(string format, params object[] args); + void Warn(string message); + void Warn(string message, System.Exception exception); + [JetBrains.Annotations.StringFormatMethodAttribute("format")] + void WarnFormat(string format, params object[] args); + } + public interface ILoggerFactory + { + NServiceBus.Logging.ILog GetLogger(System.Type type); + NServiceBus.Logging.ILog GetLogger(string name); + } + public abstract class LoggingFactoryDefinition + { + protected LoggingFactoryDefinition() { } + protected internal abstract NServiceBus.Logging.ILoggerFactory GetLoggingFactory(); + } + public enum LogLevel + { + Debug = 0, + Info = 1, + Warn = 2, + Error = 3, + Fatal = 4, + } + public class static LogManager + { + public static NServiceBus.Logging.ILog GetLogger() { } + public static NServiceBus.Logging.ILog GetLogger(System.Type type) { } + public static NServiceBus.Logging.ILog GetLogger(string name) { } + public static T Use() + where T : NServiceBus.Logging.LoggingFactoryDefinition, new () { } + public static void UseFactory(NServiceBus.Logging.ILoggerFactory loggerFactory) { } + } +} +namespace NServiceBus.MessageInterfaces +{ + public interface IMessageMapper : NServiceBus.IMessageCreator + { + System.Type GetMappedTypeFor(System.Type t); + System.Type GetMappedTypeFor(string typeName); + void Initialize(System.Collections.Generic.IEnumerable types); + } +} +namespace NServiceBus.MessageInterfaces.MessageMapper.Reflection +{ + public class MessageMapper : NServiceBus.IMessageCreator, NServiceBus.MessageInterfaces.IMessageMapper + { + public MessageMapper() { } + public T CreateInstance(System.Action action) { } + public T CreateInstance() { } + public object CreateInstance(System.Type t) { } + public System.Type GetMappedTypeFor(System.Type t) { } + public System.Type GetMappedTypeFor(string typeName) { } + public void Initialize(System.Collections.Generic.IEnumerable types) { } + } +} +namespace NServiceBus.MessageMutator +{ + [System.ObsoleteAttribute("Have the mutator implement both IMutateOutgoingMessages and IMutateIncomingMessag" + + "es. Will be removed in version 7.0.0.", true)] + public interface IMessageMutator { } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IMutateIncomingMessages + { + System.Threading.Tasks.Task MutateIncoming(NServiceBus.MessageMutator.MutateIncomingMessageContext context); + } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IMutateIncomingTransportMessages + { + System.Threading.Tasks.Task MutateIncoming(NServiceBus.MessageMutator.MutateIncomingTransportMessageContext context); + } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IMutateOutgoingMessages + { + System.Threading.Tasks.Task MutateOutgoing(NServiceBus.MessageMutator.MutateOutgoingMessageContext context); + } + [JetBrains.Annotations.UsedImplicitlyAttribute(JetBrains.Annotations.ImplicitUseTargetFlags.Default | JetBrains.Annotations.ImplicitUseTargetFlags.Itself | JetBrains.Annotations.ImplicitUseTargetFlags.Members | JetBrains.Annotations.ImplicitUseTargetFlags.WithMembers)] + public interface IMutateOutgoingTransportMessages + { + System.Threading.Tasks.Task MutateOutgoing(NServiceBus.MessageMutator.MutateOutgoingTransportMessageContext context); + } + [System.ObsoleteAttribute("Have the mutator implement both IMutateIncomingTransportMessages and IMutateOutgo" + + "ingTransportMessages. Will be removed in version 7.0.0.", true)] + public interface IMutateTransportMessages : NServiceBus.MessageMutator.IMutateIncomingTransportMessages, NServiceBus.MessageMutator.IMutateOutgoingTransportMessages { } + public class MutateIncomingMessageContext + { + public MutateIncomingMessageContext(object message, System.Collections.Generic.Dictionary headers) { } + public System.Collections.Generic.Dictionary Headers { get; } + public object Message { get; set; } + } + public class MutateIncomingTransportMessageContext + { + public MutateIncomingTransportMessageContext(byte[] body, System.Collections.Generic.Dictionary headers) { } + public byte[] Body { get; set; } + public System.Collections.Generic.Dictionary Headers { get; } + } + public class MutateOutgoingMessageContext + { + public MutateOutgoingMessageContext(object outgoingMessage, System.Collections.Generic.Dictionary outgoingHeaders, object incomingMessage, System.Collections.Generic.IReadOnlyDictionary incomingHeaders) { } + public System.Collections.Generic.Dictionary OutgoingHeaders { get; } + public object OutgoingMessage { get; set; } + public bool TryGetIncomingHeaders(out System.Collections.Generic.IReadOnlyDictionary<, > incomingHeaders) { } + public bool TryGetIncomingMessage(out object incomingMessage) { } + } + public class MutateOutgoingTransportMessageContext + { + public MutateOutgoingTransportMessageContext(byte[] outgoingBody, object outgoingMessage, System.Collections.Generic.Dictionary outgoingHeaders, object incomingMessage, System.Collections.Generic.IReadOnlyDictionary incomingHeaders) { } + public byte[] OutgoingBody { get; set; } + public System.Collections.Generic.Dictionary OutgoingHeaders { get; } + public object OutgoingMessage { get; } + public bool TryGetIncomingHeaders(out System.Collections.Generic.IReadOnlyDictionary<, > incomingHeaders) { } + public bool TryGetIncomingMessage(out object incomingMessage) { } + } +} +namespace NServiceBus.ObjectBuilder.Common +{ + public interface IContainer : System.IDisposable + { + object Build(System.Type typeToBuild); + System.Collections.Generic.IEnumerable BuildAll(System.Type typeToBuild); + NServiceBus.ObjectBuilder.Common.IContainer BuildChildContainer(); + void Configure(System.Type component, NServiceBus.DependencyLifecycle dependencyLifecycle); + void Configure(System.Func component, NServiceBus.DependencyLifecycle dependencyLifecycle); + bool HasComponent(System.Type componentType); + void RegisterSingleton(System.Type lookupType, object instance); + void Release(object instance); + } +} +namespace NServiceBus.ObjectBuilder +{ + public interface IBuilder : System.IDisposable + { + object Build(System.Type typeToBuild); + T Build(); + System.Collections.Generic.IEnumerable BuildAll(); + System.Collections.Generic.IEnumerable BuildAll(System.Type typeToBuild); + void BuildAndDispatch(System.Type typeToBuild, System.Action action); + NServiceBus.ObjectBuilder.IBuilder CreateChildBuilder(); + void Release(object instance); + } + [System.ObsoleteAttribute("Setting property values explicitly is no longer supported via this API. Use `.Con" + + "figureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full contro" + + "l over handler creation. Will be removed in version 7.0.0.", true)] + public interface IComponentConfig { } + [System.ObsoleteAttribute("Setting property values explicitly is no longer supported via this API. Use `.Con" + + "figureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full contro" + + "l over handler creation. Will be removed in version 7.0.0.", true)] + public interface IComponentConfig { } + [System.ObsoleteAttribute("Setting property values explicitly is no longer supported via this API. Use `.Con" + + "figureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full contro" + + "l over handler creation. Will be removed in version 7.0.0.", true)] + public class static IConfigureComponentObsoleteExtensions + { + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static NServiceBus.ObjectBuilder.IConfigureComponents ConfigureProperty(this NServiceBus.ObjectBuilder.IConfigureComponents config, System.Linq.Expressions.Expression> property, object value) { } + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static NServiceBus.ObjectBuilder.IConfigureComponents ConfigureProperty(this NServiceBus.ObjectBuilder.IConfigureComponents config, string propertyName, object value) { } + } + public interface IConfigureComponents + { + void ConfigureComponent(System.Type concreteComponent, NServiceBus.DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(NServiceBus.DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(System.Func componentFactory, NServiceBus.DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(System.Func componentFactory, NServiceBus.DependencyLifecycle dependencyLifecycle); + bool HasComponent(); + bool HasComponent(System.Type componentType); + void RegisterSingleton(System.Type lookupType, object instance); + void RegisterSingleton(T instance); + } +} +namespace NServiceBus.Outbox +{ + public interface IOutboxStorage + { + System.Threading.Tasks.Task BeginTransaction(NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Get(string messageId, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task SetAsDispatched(string messageId, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Store(NServiceBus.Outbox.OutboxMessage message, NServiceBus.Outbox.OutboxTransaction transaction, NServiceBus.Extensibility.ContextBag context); + } + public class OutboxMessage + { + public OutboxMessage(string messageId, NServiceBus.Outbox.TransportOperation[] operations) { } + public string MessageId { get; } + public NServiceBus.Outbox.TransportOperation[] TransportOperations { get; } + } + public class OutboxSettings : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + { + [System.ObsoleteAttribute("Use `InMemoryOutboxSettingsExtensions.TimeToKeepDeduplicationData(TimeSpan time)`" + + " instead. The member currently throws a NotImplementedException. Will be removed" + + " in version 7.0.0.", true)] + public void TimeToKeepDeduplicationData(System.TimeSpan time) { } + } + public interface OutboxTransaction : System.IDisposable + { + System.Threading.Tasks.Task Commit(); + } + public class TransportOperation + { + public TransportOperation(string messageId, System.Collections.Generic.Dictionary options, byte[] body, System.Collections.Generic.Dictionary headers) { } + public byte[] Body { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + public System.Collections.Generic.Dictionary Options { get; } + } +} +namespace NServiceBus.Performance.TimeToBeReceived +{ + public class DiscardIfNotReceivedBefore : NServiceBus.DeliveryConstraints.DeliveryConstraint + { + public DiscardIfNotReceivedBefore(System.TimeSpan maxTime) { } + public System.TimeSpan MaxTime { get; } + } +} +namespace NServiceBus.Persistence +{ + public interface CompletableSynchronizedStorageSession : NServiceBus.Persistence.SynchronizedStorageSession, System.IDisposable + { + System.Threading.Tasks.Task CompleteAsync(); + } + public interface ISynchronizedStorage + { + System.Threading.Tasks.Task OpenSession(NServiceBus.Extensibility.ContextBag contextBag); + } + public interface ISynchronizedStorageAdapter + { + System.Threading.Tasks.Task TryAdapt(NServiceBus.Outbox.OutboxTransaction transaction, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task TryAdapt(NServiceBus.Transport.TransportTransaction transportTransaction, NServiceBus.Extensibility.ContextBag context); + } + public abstract class PersistenceDefinition + { + protected PersistenceDefinition() { } + protected void Defaults(System.Action action) { } + [System.ObsoleteAttribute("Use `HasSupportFor()` instead. The member currently throws a NotImplementedExc" + + "eption. Will be removed in version 7.0.0.", true)] + public bool HasSupportFor(NServiceBus.Persistence.Storage storage) { } + public bool HasSupportFor() + where T : NServiceBus.Persistence.StorageType { } + public bool HasSupportFor(System.Type storageType) { } + protected void Supports(System.Action action) + where T : NServiceBus.Persistence.StorageType { } + [System.ObsoleteAttribute("Use `Supports()` instead. The member currently throws a NotImplementedExceptio" + + "n. Will be removed in version 7.0.0.", true)] + protected void Supports(NServiceBus.Persistence.Storage storage, System.Action action) { } + } + [System.ObsoleteAttribute("Use `NServiceBus.Persistence.StorageType` instead. Will be removed in version 7.0" + + ".0.", true)] + public enum Storage + { + Timeouts = 1, + Subscriptions = 2, + Sagas = 3, + GatewayDeduplication = 4, + Outbox = 5, + } + public abstract class StorageType + { + public override string ToString() { } + public sealed class GatewayDeduplication : NServiceBus.Persistence.StorageType { } + public sealed class Outbox : NServiceBus.Persistence.StorageType { } + public sealed class Sagas : NServiceBus.Persistence.StorageType { } + public sealed class Subscriptions : NServiceBus.Persistence.StorageType { } + public sealed class Timeouts : NServiceBus.Persistence.StorageType { } + } + public interface SynchronizedStorageSession { } +} +namespace NServiceBus.Persistence.Legacy +{ + public class MsmqPersistence : NServiceBus.Persistence.PersistenceDefinition { } +} +namespace NServiceBus.Pipeline +{ + public abstract class Behavior : NServiceBus.Pipeline.IBehavior, NServiceBus.Pipeline.IBehavior + where TContext : NServiceBus.Pipeline.IBehaviorContext + { + protected Behavior() { } + public System.Threading.Tasks.Task Invoke(TContext context, System.Func next) { } + public abstract System.Threading.Tasks.Task Invoke(TContext context, System.Func next); + } + public abstract class ForkConnector : NServiceBus.Pipeline.Behavior, NServiceBus.IForkConnector, NServiceBus.IForkConnector, NServiceBus.Pipeline.IBehavior, NServiceBus.Pipeline.IBehavior + where TFromContext : NServiceBus.Pipeline.IBehaviorContext + where TForkContext : NServiceBus.Pipeline.IBehaviorContext + { + protected ForkConnector() { } + public abstract System.Threading.Tasks.Task Invoke(TFromContext context, System.Func next, System.Func fork); + public virtual System.Threading.Tasks.Task Invoke(TFromContext context, System.Func next) { } + } + public interface IAuditContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + string AuditAddress { get; } + NServiceBus.Transport.OutgoingMessage Message { get; } + void AddAuditData(string key, string value); + } + public interface IBatchDispatchContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + System.Collections.Generic.IReadOnlyCollection Operations { get; } + } + public interface IBehavior { } + [System.ObsoleteAttribute("Use `Behavior` instead. Will be removed in version 7.0.0.", true)] + public interface IBehavior { } + public interface IBehavior : NServiceBus.Pipeline.IBehavior + where in TInContext : NServiceBus.Pipeline.IBehaviorContext + where out TOutContext : NServiceBus.Pipeline.IBehaviorContext + { + System.Threading.Tasks.Task Invoke(TInContext context, System.Func next); + } + public interface IBehaviorContext : NServiceBus.Extensibility.IExtendable + { + NServiceBus.ObjectBuilder.IBuilder Builder { get; } + } + public interface IDispatchContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + System.Collections.Generic.IEnumerable Operations { get; } + } + public interface IForwardingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + string Address { get; } + NServiceBus.Transport.OutgoingMessage Message { get; } + } + public interface IIncomingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IMessageProcessingContext, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext { } + public interface IIncomingLogicalMessageContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IMessageProcessingContext, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IIncomingContext + { + System.Collections.Generic.Dictionary Headers { get; } + NServiceBus.Pipeline.LogicalMessage Message { get; } + bool MessageHandled { get; set; } + void UpdateMessageInstance(object newInstance); + } + public interface IIncomingPhysicalMessageContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IMessageProcessingContext, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IIncomingContext + { + NServiceBus.Transport.IncomingMessage Message { get; } + void UpdateMessage(byte[] body); + } + public interface IInvokeHandlerContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IMessageHandlerContext, NServiceBus.IMessageProcessingContext, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IIncomingContext + { + bool HandleCurrentMessageLaterWasCalled { get; } + bool HandlerInvocationAborted { get; } + System.Collections.Generic.Dictionary Headers { get; } + object MessageBeingHandled { get; } + NServiceBus.Pipeline.MessageHandler MessageHandler { get; } + NServiceBus.Unicast.Messages.MessageMetadata MessageMetadata { get; } + } + public interface IOutgoingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext + { + System.Collections.Generic.Dictionary Headers { get; } + string MessageId { get; } + } + public interface IOutgoingLogicalMessageContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IOutgoingContext + { + NServiceBus.Pipeline.OutgoingLogicalMessage Message { get; } + System.Collections.Generic.IReadOnlyCollection RoutingStrategies { get; } + void UpdateMessage(object newInstance); + } + public interface IOutgoingPhysicalMessageContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IOutgoingContext + { + byte[] Body { get; } + System.Collections.Generic.IReadOnlyCollection RoutingStrategies { get; } + void UpdateMessage(byte[] body); + } + public interface IOutgoingPublishContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IOutgoingContext + { + NServiceBus.Pipeline.OutgoingLogicalMessage Message { get; } + } + public interface IOutgoingReplyContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IOutgoingContext + { + NServiceBus.Pipeline.OutgoingLogicalMessage Message { get; } + } + public interface IOutgoingSendContext : NServiceBus.Extensibility.IExtendable, NServiceBus.IPipelineContext, NServiceBus.Pipeline.IBehaviorContext, NServiceBus.Pipeline.IOutgoingContext + { + NServiceBus.Pipeline.OutgoingLogicalMessage Message { get; } + } + public interface IRoutingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + NServiceBus.Transport.OutgoingMessage Message { get; } + System.Collections.Generic.IReadOnlyCollection RoutingStrategies { get; set; } + } + public interface ISubscribeContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + System.Type EventType { get; } + } + public interface ITransportReceiveContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + NServiceBus.Transport.IncomingMessage Message { get; } + void AbortReceiveOperation(); + } + public interface IUnsubscribeContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + { + System.Type EventType { get; } + } + public class LogicalMessage + { + public LogicalMessage(NServiceBus.Unicast.Messages.MessageMetadata metadata, object message) { } + public object Instance { get; } + public System.Type MessageType { get; } + public NServiceBus.Unicast.Messages.MessageMetadata Metadata { get; } + [System.ObsoleteAttribute("Use `IIncomingLogicalMessageContext.UpdateMessageInstance(object newInstance)` in" + + "stead. The member currently throws a NotImplementedException. Will be removed in" + + " version 7.0.0.", true)] + public void UpdateMessageInstance(object newInstance) { } + } + public class LogicalMessageFactory + { + public LogicalMessageFactory(NServiceBus.Unicast.Messages.MessageMetadataRegistry messageMetadataRegistry, NServiceBus.MessageInterfaces.IMessageMapper messageMapper) { } + public NServiceBus.Pipeline.LogicalMessage Create(object message) { } + public NServiceBus.Pipeline.LogicalMessage Create(System.Type messageType, object message) { } + } + public class MessageHandler + { + public MessageHandler(System.Func invocation, System.Type handlerType) { } + public System.Type HandlerType { get; } + public object Instance { get; set; } + public System.Threading.Tasks.Task Invoke(object message, NServiceBus.IMessageHandlerContext handlerContext) { } + } + public class OutgoingLogicalMessage + { + public OutgoingLogicalMessage(System.Type messageType, object message) { } + public object Instance { get; } + public System.Type MessageType { get; } + } + [System.ObsoleteAttribute("The pipeline context is no longer avaliable via dependency injection. Use a custo" + + "m behavior as described in the version 6 upgrade guide. Will be removed in versi" + + "on 7.0.0.", true)] + public class PipelineExecutor + { + public PipelineExecutor() { } + } + public class PipelineSettings + { + public void Register(System.Type behavior, string description) { } + public void Register(string stepId, System.Type behavior, string description) { } + public void Register(System.Func factoryMethod, string description) + where T : NServiceBus.Pipeline.IBehavior { } + public void Register(string stepId, System.Func factoryMethod, string description) + where T : NServiceBus.Pipeline.IBehavior { } + public void Register(T behavior, string description) + where T : NServiceBus.Pipeline.IBehavior { } + public void Register(string stepId, T behavior, string description) + where T : NServiceBus.Pipeline.IBehavior { } + public void Register() + where TRegisterStep : NServiceBus.Pipeline.RegisterStep, new () { } + public void Register(NServiceBus.Pipeline.RegisterStep registration) { } + public void Remove(string stepId) { } + [System.ObsoleteAttribute("Use `Remove(string stepId)` instead. The member currently throws a NotImplemented" + + "Exception. Will be removed in version 7.0.0.", true)] + public void Remove(NServiceBus.Pipeline.WellKnownStep wellKnownStep) { } + public void Replace(string stepId, System.Type newBehavior, string description = null) { } + public void Replace(string stepId, T newBehavior, string description = null) + where T : NServiceBus.Pipeline.IBehavior { } + public void Replace(string stepId, System.Func factoryMethod, string description = null) + where T : NServiceBus.Pipeline.IBehavior { } + [System.ObsoleteAttribute("Use `Replace(string stepId, Type newBehavior, string description)` instead. The m" + + "ember currently throws a NotImplementedException. Will be removed in version 7.0" + + ".0.", true)] + public void Replace(NServiceBus.Pipeline.WellKnownStep wellKnownStep, System.Type newBehavior, string description = null) { } + } + public abstract class PipelineTerminator : NServiceBus.Pipeline.StageConnector.ITerminatingContext>, NServiceBus.IPipelineTerminator + where T : NServiceBus.Pipeline.IBehaviorContext + { + protected PipelineTerminator() { } + public virtual System.Threading.Tasks.Task Invoke(T context, System.Func.ITerminatingContext, System.Threading.Tasks.Task> next) { } + protected abstract System.Threading.Tasks.Task Terminate(T context); + public interface ITerminatingContext : NServiceBus.Extensibility.IExtendable, NServiceBus.Pipeline.IBehaviorContext + where T : NServiceBus.Pipeline.IBehaviorContext { } + } + [System.Diagnostics.DebuggerDisplayAttribute("{StepId}({BehaviorType.FullName}) - {Description}")] + public abstract class RegisterStep + { + protected RegisterStep(string stepId, System.Type behavior, string description, System.Func factoryMethod = null) { } + public System.Type BehaviorType { get; } + public string Description { get; } + public string StepId { get; } + public void InsertAfter(string id) { } + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. The member currently throws a Not" + + "ImplementedException. Will be removed in version 7.0.0.", true)] + public void InsertAfter(NServiceBus.Pipeline.WellKnownStep step) { } + public void InsertAfterIfExists(string id) { } + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. The member currently throws a Not" + + "ImplementedException. Will be removed in version 7.0.0.", true)] + public void InsertAfterIfExists(NServiceBus.Pipeline.WellKnownStep step) { } + public void InsertBefore(string id) { } + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. The member currently throws a Not" + + "ImplementedException. Will be removed in version 7.0.0.", true)] + public void InsertBefore(NServiceBus.Pipeline.WellKnownStep step) { } + public void InsertBeforeIfExists(string id) { } + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. The member currently throws a Not" + + "ImplementedException. Will be removed in version 7.0.0.", true)] + public void InsertBeforeIfExists(NServiceBus.Pipeline.WellKnownStep step) { } + public virtual bool IsEnabled(NServiceBus.Settings.ReadOnlySettings settings) { } + } + public abstract class StageConnector : NServiceBus.IStageConnector, NServiceBus.IStageConnector, NServiceBus.Pipeline.IBehavior, NServiceBus.Pipeline.IBehavior + where TFromContext : NServiceBus.Pipeline.IBehaviorContext + where TToContext : NServiceBus.Pipeline.IBehaviorContext + { + protected StageConnector() { } + public abstract System.Threading.Tasks.Task Invoke(TFromContext context, System.Func stage); + } + public abstract class StageForkConnector : NServiceBus.IForkConnector, NServiceBus.IForkConnector, NServiceBus.IStageConnector, NServiceBus.IStageForkConnector, NServiceBus.Pipeline.IBehavior, NServiceBus.Pipeline.IBehavior + where TFromContext : NServiceBus.Pipeline.IBehaviorContext + where TToContext : NServiceBus.Pipeline.IBehaviorContext + where TForkContext : NServiceBus.Pipeline.IBehaviorContext + { + protected StageForkConnector() { } + public System.Threading.Tasks.Task Invoke(TFromContext context, System.Func next) { } + public abstract System.Threading.Tasks.Task Invoke(TFromContext context, System.Func stage, System.Func fork); + } + public class static TransportMessageContextExtensions + { + public static bool TryGetIncomingPhysicalMessage(this NServiceBus.Pipeline.IOutgoingReplyContext context, out NServiceBus.Transport.IncomingMessage message) { } + public static bool TryGetIncomingPhysicalMessage(this NServiceBus.Pipeline.IOutgoingLogicalMessageContext context, out NServiceBus.Transport.IncomingMessage message) { } + public static bool TryGetIncomingPhysicalMessage(this NServiceBus.Pipeline.IOutgoingPhysicalMessageContext context, out NServiceBus.Transport.IncomingMessage message) { } + } + [System.ObsoleteAttribute("WellKnownSteps are obsolete. Use an appropriate pipeline stage for your behavior " + + "instead. Consult the pipeline extension documentation for more information. Will" + + " be removed in version 7.0.0.", true)] + public class WellKnownStep + { + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep AuditProcessedMessage; + [System.ObsoleteAttribute("The child container creation is now an integral part of the pipeline invocation a" + + "nd no longer a separate behavior. Will be removed in version 7.0.0.", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep CreateChildContainer; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep DispatchMessageToTransport; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep EnforcePublishBestPractices; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep EnforceReplyBestPractices; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep EnforceSendBestPractices; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep EnforceSubscribeBestPractices; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep EnforceUnsubscribeBestPractices; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep ExecuteUnitOfWork; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static NServiceBus.Pipeline.WellKnownStep HostInformation; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep InvokeHandlers; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep InvokeSaga; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep MutateIncomingMessages; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep MutateIncomingTransportMessage; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep MutateOutgoingMessages; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static readonly NServiceBus.Pipeline.WellKnownStep MutateOutgoingTransportMessage; + [System.ObsoleteAttribute("Use an appropriate pipeline stage for your behavior instead. Consult the pipeline" + + " extension documentation for more information. Will be removed in version 7.0.0." + + "", true)] + public static NServiceBus.Pipeline.WellKnownStep ProcessingStatistics; + public WellKnownStep() { } + } +} +namespace NServiceBus.Pipeline.Contexts +{ + [System.ObsoleteAttribute("Use `OutgoingLogicalMessage` instead. Will be removed in version 7.0.0.", true)] + public class OutgoingContext + { + public OutgoingContext() { } + } +} +namespace NServiceBus.Routing +{ + public abstract class AddressTag + { + protected AddressTag() { } + } + public abstract class DistributionStrategy + { + protected DistributionStrategy(string endpoint, NServiceBus.DistributionStrategyScope scope) { } + public string Endpoint { get; } + public NServiceBus.DistributionStrategyScope Scope { get; } + public abstract string SelectReceiver(string[] receiverAddresses); + } + public sealed class EndpointInstance + { + public EndpointInstance(string endpoint, string discriminator = null, System.Collections.Generic.IReadOnlyDictionary properties = null) { } + public string Discriminator { get; } + public string Endpoint { get; } + public System.Collections.Generic.IReadOnlyDictionary Properties { get; } + public override bool Equals(object obj) { } + public override int GetHashCode() { } + public NServiceBus.Routing.EndpointInstance SetProperty(string key, string value) { } + public override string ToString() { } + } + public class EndpointInstances + { + public EndpointInstances() { } + public void AddOrReplaceInstances(string sourceKey, System.Collections.Generic.IList endpointInstances) { } + } + public interface IMessageDrivenSubscriptionTransport { } + public class MulticastAddressTag : NServiceBus.Routing.AddressTag + { + public MulticastAddressTag(System.Type messageType) { } + public System.Type MessageType { get; } + } + public class MulticastRoutingStrategy : NServiceBus.Routing.RoutingStrategy + { + public MulticastRoutingStrategy(System.Type messageType) { } + public override NServiceBus.Routing.AddressTag Apply(System.Collections.Generic.Dictionary headers) { } + } + public class RouteTableEntry + { + public RouteTableEntry(System.Type messageType, NServiceBus.Routing.UnicastRoute route) { } + public System.Type MessageType { get; } + public NServiceBus.Routing.UnicastRoute Route { get; } + } + public abstract class RoutingStrategy + { + protected RoutingStrategy() { } + public abstract NServiceBus.Routing.AddressTag Apply(System.Collections.Generic.Dictionary headers); + } + public class SingleInstanceRoundRobinDistributionStrategy : NServiceBus.Routing.DistributionStrategy + { + public SingleInstanceRoundRobinDistributionStrategy(string endpoint, NServiceBus.DistributionStrategyScope scope) { } + public override string SelectReceiver(string[] receiverAddresses) { } + } + public class UnicastAddressTag : NServiceBus.Routing.AddressTag + { + public UnicastAddressTag(string destination) { } + public string Destination { get; } + } + public class UnicastRoute + { + public string Endpoint { get; } + public NServiceBus.Routing.EndpointInstance Instance { get; } + public string PhysicalAddress { get; } + public static NServiceBus.Routing.UnicastRoute CreateFromEndpointInstance(NServiceBus.Routing.EndpointInstance instance) { } + public static NServiceBus.Routing.UnicastRoute CreateFromEndpointName(string endpoint) { } + public static NServiceBus.Routing.UnicastRoute CreateFromPhysicalAddress(string physicalAddress) { } + public override string ToString() { } + } + public class UnicastRoutingStrategy : NServiceBus.Routing.RoutingStrategy + { + public UnicastRoutingStrategy(string destination) { } + public override NServiceBus.Routing.AddressTag Apply(System.Collections.Generic.Dictionary headers) { } + } + public class UnicastRoutingTable + { + public UnicastRoutingTable() { } + public void AddOrReplaceRoutes(string sourceKey, System.Collections.Generic.IList entries) { } + } +} +namespace NServiceBus.Routing.Legacy +{ + public class static ConfigureMSMQDistributor + { + public static void EnlistWithLegacyMSMQDistributor(this NServiceBus.EndpointConfiguration config, string masterNodeAddress, string masterNodeControlAddress, int capacity) { } + } +} +namespace NServiceBus.Routing.MessageDrivenSubscriptions +{ + public class PublisherAddress + { + public static NServiceBus.Routing.MessageDrivenSubscriptions.PublisherAddress CreateFromEndpointInstances(params NServiceBus.Routing.EndpointInstance[] instances) { } + public static NServiceBus.Routing.MessageDrivenSubscriptions.PublisherAddress CreateFromEndpointName(string endpoint) { } + public static NServiceBus.Routing.MessageDrivenSubscriptions.PublisherAddress CreateFromPhysicalAddresses(params string[] addresses) { } + public override bool Equals(object obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public class Publishers + { + public Publishers() { } + public void AddOrReplacePublishers(string sourceKey, System.Collections.Generic.IList entries) { } + } + public class PublisherTableEntry + { + public PublisherTableEntry(System.Type eventType, NServiceBus.Routing.MessageDrivenSubscriptions.PublisherAddress address) { } + public NServiceBus.Routing.MessageDrivenSubscriptions.PublisherAddress Address { get; } + public System.Type EventType { get; } + } +} +namespace NServiceBus.Routing.StorageDrivenPublishing +{ + [System.ObsoleteAttribute("No longer an extension point, if you want to list events without subscribers you " + + "can take a dependency on ISubscriptionStorage and query it for the event types y" + + "ou want to check. Will be removed in version 7.0.0.", true)] + public class SubscribersForEvent + { + public SubscribersForEvent(System.Collections.Generic.List subscribers, System.Type eventType) { } + public System.Type EventType { get; } + public System.Collections.Generic.IEnumerable Subscribers { get; } + } +} +namespace NServiceBus.Sagas +{ + public class ActiveSagaInstance + { + public System.DateTime Created { get; } + public NServiceBus.Saga Instance { get; } + public bool IsNew { get; } + public System.DateTime Modified { get; } + public bool NotFound { get; } + public string SagaId { get; } + [System.ObsoleteAttribute("Use `.Metadata.SagaType` instead. The member currently throws a NotImplementedExc" + + "eption. Will be removed in version 7.0.0.", true)] + public System.Type SagaType { get; } + public void AttachNewEntity(NServiceBus.IContainSagaData sagaEntity) { } + } + public interface IFinder { } + public abstract class IFindSagas + where T : NServiceBus.IContainSagaData + { + protected IFindSagas() { } + public interface Using : NServiceBus.Sagas.IFinder + where T : NServiceBus.IContainSagaData + { + System.Threading.Tasks.Task FindBy(M message, NServiceBus.Persistence.SynchronizedStorageSession storageSession, NServiceBus.Extensibility.ReadOnlyContextBag context); + } + } + public interface IHandleSagaNotFound + { + System.Threading.Tasks.Task Handle(object message, NServiceBus.IMessageProcessingContext context); + } + public interface ISagaPersister + { + System.Threading.Tasks.Task Complete(NServiceBus.IContainSagaData sagaData, NServiceBus.Persistence.SynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Get(System.Guid sagaId, NServiceBus.Persistence.SynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context) + where TSagaData : NServiceBus.IContainSagaData; + System.Threading.Tasks.Task Get(string propertyName, object propertyValue, NServiceBus.Persistence.SynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context) + where TSagaData : NServiceBus.IContainSagaData; + System.Threading.Tasks.Task Save(NServiceBus.IContainSagaData sagaData, NServiceBus.Sagas.SagaCorrelationProperty correlationProperty, NServiceBus.Persistence.SynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Update(NServiceBus.IContainSagaData sagaData, NServiceBus.Persistence.SynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context); + } + public class SagaCorrelationProperty + { + public SagaCorrelationProperty(string name, object value) { } + public string Name { get; } + public static NServiceBus.Sagas.SagaCorrelationProperty None { get; } + public object Value { get; } + } + public class SagaFinderDefinition + { + public System.Type MessageType { get; } + public string MessageTypeName { get; } + public System.Collections.Generic.Dictionary Properties { get; } + public System.Type Type { get; } + } + public class SagaMessage + { + public bool IsAllowedToStartSaga { get; } + public System.Type MessageType { get; } + public string MessageTypeName { get; } + } + public class SagaMetadata + { + public SagaMetadata(string name, System.Type sagaType, string entityName, System.Type sagaEntityType, NServiceBus.Sagas.SagaMetadata.CorrelationPropertyMetadata correlationProperty, System.Collections.Generic.IReadOnlyCollection messages, System.Collections.Generic.IReadOnlyCollection finders) { } + public System.Collections.Generic.IReadOnlyCollection AssociatedMessages { get; } + public string EntityName { get; } + public System.Collections.Generic.IReadOnlyCollection Finders { get; } + public string Name { get; } + public System.Type SagaEntityType { get; } + public System.Type SagaType { get; } + public static NServiceBus.Sagas.SagaMetadata Create(System.Type sagaType) { } + public static NServiceBus.Sagas.SagaMetadata Create(System.Type sagaType, System.Collections.Generic.IEnumerable availableTypes, NServiceBus.Conventions conventions) { } + public bool IsMessageAllowedToStartTheSaga(string messageType) { } + public bool TryGetCorrelationProperty(out NServiceBus.Sagas.SagaMetadata.CorrelationPropertyMetadata property) { } + public bool TryGetFinder(string messageType, out NServiceBus.Sagas.SagaFinderDefinition finderDefinition) { } + public class CorrelationPropertyMetadata + { + public CorrelationPropertyMetadata(string name, System.Type type) { } + public string Name { get; } + public System.Type Type { get; } + } + } + public class SagaMetadataCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public SagaMetadataCollection() { } + public NServiceBus.Sagas.SagaMetadata Find(System.Type sagaType) { } + public NServiceBus.Sagas.SagaMetadata FindByEntity(System.Type entityType) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Initialize(System.Collections.Generic.IEnumerable availableTypes) { } + public void Initialize(System.Collections.Generic.IEnumerable availableTypes, NServiceBus.Conventions conventions) { } + } + [System.ObsoleteAttribute("There is no need for this attribute anymore, all mapped properties are automatica" + + "lly correlated. Will be removed in version 7.0.0.", true)] + public sealed class UniqueAttribute : System.Attribute + { + public UniqueAttribute() { } + [System.ObsoleteAttribute("Use the new SagaMetadata. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + public static System.Collections.Generic.IDictionary GetUniqueProperties(NServiceBus.IContainSagaData entity) { } + [System.ObsoleteAttribute("Use the new SagaMetadata. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + public static System.Collections.Generic.IEnumerable GetUniqueProperties(System.Type type) { } + [System.ObsoleteAttribute("Use the new SagaMetadata. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + public static System.Reflection.PropertyInfo GetUniqueProperty(System.Type type) { } + [System.ObsoleteAttribute("Use the new SagaMetadata. The member currently throws a NotImplementedException. " + + "Will be removed in version 7.0.0.", true)] + public static System.Nullable> GetUniqueProperty(NServiceBus.IContainSagaData entity) { } + } +} +namespace NServiceBus.Satellites +{ + [System.ObsoleteAttribute("No longer an extension point. Instead create a Feature and use FeatureConfigurati" + + "onContext.AddSatelliteReceiver(...). Will be removed in version 7.0.0.", true)] + public interface IAdvancedSatellite { } + [System.ObsoleteAttribute("No longer an extension point. Instead create a Feature and use FeatureConfigurati" + + "onContext.AddSatelliteReceiver(...). Will be removed in version 7.0.0.", true)] + public interface ISatellite { } +} +namespace NServiceBus.SecondLevelRetries.Config +{ + [System.ObsoleteAttribute("Use `NServiceBus.SecondLevelRetriesSettings` instead. Will be removed in version " + + "7.0.0.", true)] + public class SecondLevelRetriesSettings + { + public SecondLevelRetriesSettings() { } + [System.ObsoleteAttribute("Use `NServiceBus.SecondLevelRetriesSettings.CustomRetryPolicy(Func customPolicy)` instead. The member currently throws a NotImplemente" + + "dException. Will be removed in version 7.0.0.", true)] + public void CustomRetryPolicy(System.Func customPolicy) { } + } +} +namespace NServiceBus.Serialization +{ + [System.ObsoleteAttribute("To use a custom serializer derive from SerializationDefinition and provide a fact" + + "ory method for creating the serializer instance. Will be removed in version 7.0." + + "0.", true)] + public abstract class ConfigureSerialization + { + protected ConfigureSerialization() { } + } + public interface IMessageSerializer + { + string ContentType { get; } + object[] Deserialize(System.IO.Stream stream, System.Collections.Generic.IList messageTypes = null); + void Serialize(object message, System.IO.Stream stream); + } + public abstract class SerializationDefinition + { + protected SerializationDefinition() { } + public abstract System.Func Configure(NServiceBus.Settings.ReadOnlySettings settings); + } + public class SerializationExtensions : NServiceBus.Configuration.AdvanceExtensibility.ExposeSettings + where T : NServiceBus.Serialization.SerializationDefinition + { + public SerializationExtensions(NServiceBus.Settings.SettingsHolder settings) { } + } +} +namespace NServiceBus.Serializers.Json +{ + [System.ObsoleteAttribute("Built-in serializers are internal. Switch to an alternative (e.g. Json.net) or co" + + "py the serializer code. Will be removed in version 7.0.0.", true)] + public class JsonMessageSerializer + { + public JsonMessageSerializer() { } + } +} +namespace NServiceBus.Serializers.XML +{ + [System.ObsoleteAttribute("Built-in serializers are internal. Switch to an alternative (e.g. XmlSerializer)" + + " or copy the serializer code. Will be removed in version 7.0.0.", true)] + public class XmlMessageSerializer + { + public XmlMessageSerializer() { } + } +} +namespace NServiceBus.Settings.NServiceBus +{ + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class static TransactionSettingsExtentions + { + [System.ObsoleteAttribute("No longer used, can safely be removed. The member currently throws a NotImplement" + + "edException. Will be removed in version 7.0.0.", true)] + public static NServiceBus.Settings.TransactionSettings Transactions(this NServiceBus.EndpointConfiguration config) { } + } +} +namespace NServiceBus.Settings +{ + public interface ReadOnlySettings + { + T Get(); + T Get(string key); + object Get(string key); + T GetOrDefault(); + T GetOrDefault(string key); + bool HasExplicitValue(string key); + bool HasExplicitValue(); + bool HasSetting(string key); + bool HasSetting(); + bool TryGet(out T val); + bool TryGet(string key, out T val); + } + public class static ReadOnlySettingsExtensions + { + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static void ApplyTo(NServiceBus.ObjectBuilder.IComponentConfig config) { } + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public static void ApplyTo(System.Type componentType, NServiceBus.ObjectBuilder.IComponentConfig config) { } + } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public class ScaleOutSettings + { + public ScaleOutSettings() { } + [System.ObsoleteAttribute("Not required any more as for MSMQ this is the default behavior and for other tran" + + "sports the unique instance ID has to be provided. The member currently throws a " + + "NotImplementedException. Will be removed in version 7.0.0.", true)] + public void UniqueQueuePerEndpointInstance() { } + [System.ObsoleteAttribute("Use `EndpointConfiguration.MakeInstanceUniquelyAddressable(string discriminator)`" + + " instead. The member currently throws a NotImplementedException. Will be removed" + + " in version 7.0.0.", true)] + public void UniqueQueuePerEndpointInstance(string discriminator) { } + [System.ObsoleteAttribute("This is the default starting with V6. The member currently throws a NotImplemente" + + "dException. Will be removed in version 7.0.0.", true)] + public void UseSingleBrokerQueue() { } + [System.ObsoleteAttribute("Not required any more as for MSMQ this is the default behavior and for other tran" + + "sports the unique instance ID has to be provided. The member currently throws a " + + "NotImplementedException. Will be removed in version 7.0.0.", true)] + public void UseUniqueBrokerQueuePerMachine() { } + } + public class SettingsHolder : NServiceBus.Settings.ReadOnlySettings + { + public SettingsHolder() { } + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void ApplyTo(NServiceBus.ObjectBuilder.IComponentConfig config) { } + [System.ObsoleteAttribute(@"Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void ApplyTo(System.Type componentType, NServiceBus.ObjectBuilder.IComponentConfig config) { } + public void Clear() { } + public T Get(string key) { } + public T Get() { } + public object Get(string key) { } + public T GetOrCreate() + where T : class, new () { } + public T GetOrDefault() { } + public T GetOrDefault(string key) { } + public bool HasExplicitValue(string key) { } + public bool HasExplicitValue() { } + public bool HasSetting(string key) { } + public bool HasSetting() { } + public void Set(string key, object value) { } + public void Set(object value) { } + public void Set(System.Action value) { } + public void SetDefault(object value) { } + public void SetDefault(System.Action value) { } + public void SetDefault(string key, object value) { } + [System.ObsoleteAttribute("Use `Set(string key, object value)` instead. The member currently throws a NotImp" + + "lementedException. Will be removed in version 7.0.0.", true)] + public void SetProperty(System.Linq.Expressions.Expression> property, object value) { } + [System.ObsoleteAttribute("Use `Set(string key, object value)` instead. The member currently throws a NotImp" + + "lementedException. Will be removed in version 7.0.0.", true)] + public void SetPropertyDefault(System.Linq.Expressions.Expression> property, object value) { } + public bool TryGet(out T val) { } + public bool TryGet(string key, out T val) { } + } + public class TransactionSettings + { + public TransactionSettings() { } + [System.ObsoleteAttribute("Use `config.UnitOfWork().WrapHandlersInATransactionScope(timeout: TimeSpan.FromSe" + + "conds(X));` instead. The member currently throws a NotImplementedException. Will" + + " be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings DefaultTimeout(System.TimeSpan defaultTimeout) { } + [System.ObsoleteAttribute("Use `config.UseTransport().Transactions(TransportTransactionMode.Non" + + "e);` instead. The member currently throws a NotImplementedException. Will be rem" + + "oved in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings Disable() { } + [System.ObsoleteAttribute("Use `config.UseTransport().Transactions(TransportTransactionMode.Rec" + + "eiveOnly|TransportTransactionMode.SendsAtomicWithReceive);` instead. The member " + + "currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings DisableDistributedTransactions() { } + [System.ObsoleteAttribute(@"DoNotWrapHandlersExecutionInATransactionScope() has been removed since transaction scopes are no longer used by non DTC transports delay the dispatch of all outgoing operations until handlers have been executed.\r\nIn Version 6 handlers will only be wrapped in a transactionscope if running the MSMQ or SQLServer transports in default mode. This means that performing storage operations against data sources also supporting transaction scopes\r\nwill escalate to a distributed transaction. Previous versions allowed opting out of this behavior using config.Transactions().DoNotWrapHandlersExecutionInATransactionScope(). In Version 6 it's recommended to use `EndpointConfiguration.UseTransport().Transactions(TransportTransactionMode.ReceiveOnly)` to lean on native transport transaction and the new batched dispatch support to achieve the same level of consistency with better performance.\r\nSuppressing the ambient transaction created by the MSMQ and SQL Server transports can still be achieved by creating a custom pipeline behavior with a suppressed transaction scope. The member currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings DoNotWrapHandlersExecutionInATransactionScope() { } + [System.ObsoleteAttribute("Use `config.UseTransport().Transactions(TransportTransactionMode.Rec" + + "eiveOnly|TransportTransactionMode.SendsAtomicWithReceive);` instead. The member " + + "currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings Enable() { } + [System.ObsoleteAttribute("Use `config.UseTransport().Transactions(TransportTransactionMode.Tra" + + "nsactionScope);` instead. The member currently throws a NotImplementedException." + + " Will be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings EnableDistributedTransactions() { } + [System.ObsoleteAttribute("Use `config.UnitOfWork().WrapHandlersInATransactionScope(isolationLevel: Isolatio" + + "nLevel.X);` instead. The member currently throws a NotImplementedException. Will" + + " be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings IsolationLevel(System.Transactions.IsolationLevel isolationLevel) { } + [System.ObsoleteAttribute("Use `config.UnitOfWork().WrapHandlersInATransactionScope();` instead. The member " + + "currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.Settings.TransactionSettings WrapHandlersExecutionInATransactionScope() { } + } +} +namespace NServiceBus.Support +{ + public class static RuntimeEnvironment + { + public static string MachineName { get; } + public static System.Func MachineNameAction { get; set; } + } +} +namespace NServiceBus.Timeout.Core +{ + public interface IPersistTimeouts + { + System.Threading.Tasks.Task Add(NServiceBus.Timeout.Core.TimeoutData timeout, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Peek(string timeoutId, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task RemoveTimeoutBy(System.Guid sagaId, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task TryRemove(string timeoutId, NServiceBus.Extensibility.ContextBag context); + } + public interface IQueryTimeouts + { + System.Threading.Tasks.Task GetNextChunk(System.DateTime startSlice); + } + public class TimeoutData + { + [System.ObsoleteAttribute("Not used anymore. Will be removed in version 7.0.0.", true)] + public const string OriginalReplyToAddress = "NServiceBus.Timeout.ReplyToAddress"; + public TimeoutData() { } + public string Destination { get; set; } + public System.Collections.Generic.Dictionary Headers { get; set; } + public string Id { get; set; } + public string OwningTimeoutManager { get; set; } + public System.Guid SagaId { get; set; } + public byte[] State { get; set; } + public System.DateTime Time { get; set; } + [System.ObsoleteAttribute("Use new SendOptions() instead. The member currently throws a NotImplementedExcept" + + "ion. Will be removed in version 7.0.0.", true)] + public NServiceBus.Unicast.SendOptions ToSendOptions(NServiceBus.Address replyToAddress) { } + [System.ObsoleteAttribute("Use new SendOptions() instead. The member currently throws a NotImplementedExcept" + + "ion. Will be removed in version 7.0.0.", true)] + public NServiceBus.Unicast.SendOptions ToSendOptions(string replyToAddress) { } + public override string ToString() { } + [System.ObsoleteAttribute("Use new OutgoingMessage(timeoutData.State) instead. The member currently throws a" + + " NotImplementedException. Will be removed in version 7.0.0.", true)] + public NServiceBus.TransportMessage ToTransportMessage() { } + } + public class TimeoutsChunk + { + public TimeoutsChunk(Timeout[] dueTimeouts, System.DateTime nextTimeToQuery) { } + public Timeout[] DueTimeouts { get; } + public System.DateTime NextTimeToQuery { get; } + public struct Timeout + { + public Timeout(string id, System.DateTime dueTime) { } + public System.DateTime DueTime { get; } + public string Id { get; } + } + } +} +namespace NServiceBus.Transport +{ + public enum DispatchConsistency + { + Default = 1, + Isolated = 2, + } + public class ErrorContext + { + public ErrorContext(System.Exception exception, System.Collections.Generic.Dictionary headers, string transportMessageId, byte[] body, NServiceBus.Transport.TransportTransaction transportTransaction, int immediateProcessingFailures) { } + public int DelayedDeliveriesPerformed { get; } + public System.Exception Exception { get; } + public int ImmediateProcessingFailures { get; } + public NServiceBus.Transport.IncomingMessage Message { get; } + public NServiceBus.Transport.TransportTransaction TransportTransaction { get; } + } + public enum ErrorHandleResult + { + Handled = 0, + RetryRequired = 1, + } + public interface ICancelDeferredMessages + { + System.Threading.Tasks.Task CancelDeferredMessages(string messageKey, NServiceBus.Pipeline.IBehaviorContext context); + } + public interface ICreateQueues + { + System.Threading.Tasks.Task CreateQueueIfNecessary(NServiceBus.Transport.QueueBindings queueBindings, string identity); + } + public interface IDispatchMessages + { + System.Threading.Tasks.Task Dispatch(NServiceBus.Transport.TransportOperations outgoingMessages, NServiceBus.Transport.TransportTransaction transaction, NServiceBus.Extensibility.ContextBag context); + } + public interface IManageSubscriptions + { + System.Threading.Tasks.Task Subscribe(System.Type eventType, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Unsubscribe(System.Type eventType, NServiceBus.Extensibility.ContextBag context); + } + public class IncomingMessage + { + public IncomingMessage(string messageId, System.Collections.Generic.Dictionary headers, byte[] body) { } + public byte[] Body { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + } + public class static IncomingMessageExtensions + { + public static NServiceBus.MessageIntentEnum GetMesssageIntent(this NServiceBus.Transport.IncomingMessage message) { } + public static string GetReplyToAddress(this NServiceBus.Transport.IncomingMessage message) { } + } + public interface IOutgoingTransportOperation + { + System.Collections.Generic.List DeliveryConstraints { get; } + NServiceBus.Transport.OutgoingMessage Message { get; } + NServiceBus.Transport.DispatchConsistency RequiredDispatchConsistency { get; } + } + public interface IPushMessages + { + System.Threading.Tasks.Task Init(System.Func onMessage, System.Func> onError, NServiceBus.CriticalError criticalError, NServiceBus.Transport.PushSettings settings); + void Start(NServiceBus.Transport.PushRuntimeSettings limitations); + System.Threading.Tasks.Task Stop(); + } + public class static LogicalAddressExtensions + { + public static string GetTransportAddress(this NServiceBus.Settings.ReadOnlySettings settings, NServiceBus.LogicalAddress logicalAddress) { } + } + public class MessageContext + { + public MessageContext(string messageId, System.Collections.Generic.Dictionary headers, byte[] body, NServiceBus.Transport.TransportTransaction transportTransaction, System.Threading.CancellationTokenSource receiveCancellationTokenSource, NServiceBus.Extensibility.ContextBag context) { } + public byte[] Body { get; } + public NServiceBus.Extensibility.ContextBag Context { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + public System.Threading.CancellationTokenSource ReceiveCancellationTokenSource { get; } + public NServiceBus.Transport.TransportTransaction TransportTransaction { get; } + } + public class MulticastTransportOperation : NServiceBus.Transport.IOutgoingTransportOperation + { + public MulticastTransportOperation(NServiceBus.Transport.OutgoingMessage message, System.Type messageType, NServiceBus.Transport.DispatchConsistency requiredDispatchConsistency = 1, System.Collections.Generic.List deliveryConstraints = null) { } + public System.Collections.Generic.List DeliveryConstraints { get; } + public NServiceBus.Transport.OutgoingMessage Message { get; } + public System.Type MessageType { get; } + public NServiceBus.Transport.DispatchConsistency RequiredDispatchConsistency { get; } + } + public class OutboundRoutingPolicy + { + public OutboundRoutingPolicy(NServiceBus.Transport.OutboundRoutingType sends, NServiceBus.Transport.OutboundRoutingType publishes, NServiceBus.Transport.OutboundRoutingType replies) { } + public NServiceBus.Transport.OutboundRoutingType Publishes { get; } + public NServiceBus.Transport.OutboundRoutingType Replies { get; } + public NServiceBus.Transport.OutboundRoutingType Sends { get; } + } + public enum OutboundRoutingType + { + Unicast = 0, + Multicast = 1, + } + public class OutgoingMessage + { + public OutgoingMessage(string messageId, System.Collections.Generic.Dictionary headers, byte[] body) { } + public byte[] Body { get; } + public System.Collections.Generic.Dictionary Headers { get; } + public string MessageId { get; } + } + public class PushRuntimeSettings + { + public PushRuntimeSettings() { } + public PushRuntimeSettings(int maxConcurrency) { } + public static NServiceBus.Transport.PushRuntimeSettings Default { get; } + public int MaxConcurrency { get; } + } + public class PushSettings + { + public PushSettings(string inputQueue, string errorQueue, bool purgeOnStartup, NServiceBus.TransportTransactionMode requiredTransactionMode) { } + public string ErrorQueue { get; } + public string InputQueue { get; } + public bool PurgeOnStartup { get; } + public NServiceBus.TransportTransactionMode RequiredTransactionMode { get; } + } + public class QueueBindings + { + public QueueBindings() { } + public System.Collections.Generic.IReadOnlyCollection ReceivingAddresses { get; } + public System.Collections.Generic.IReadOnlyCollection SendingAddresses { get; } + public void BindReceiving(string address) { } + public void BindSending(string transportAddress) { } + } + public class StartupCheckResult + { + public static readonly NServiceBus.Transport.StartupCheckResult Success; + public string ErrorMessage { get; } + public bool Succeeded { get; } + public static NServiceBus.Transport.StartupCheckResult Failed(string errorMessage) { } + } + public abstract class TransportDefinition + { + protected TransportDefinition() { } + public abstract string ExampleConnectionStringForErrorMessage { get; } + [System.ObsoleteAttribute("Use TransportInfrastructure.OutboundRoutingPolicy.Publishes == OutboundRoutingTyp" + + "e.Multicast instead. The member currently throws a NotImplementedException. Will" + + " be removed in version 7.0.0.", true)] + public bool HasNativePubSubSupport { get; set; } + [System.ObsoleteAttribute("The concept of centralized publish and subscribe is no longer available. The memb" + + "er currently throws a NotImplementedException. Will be removed in version 7.0.0." + + "", true)] + public bool HasSupportForCentralizedPubSub { get; set; } + [System.ObsoleteAttribute("Use TransportInfrastructure.TransactionMode == TransportTransactionMode.Transacti" + + "onScope instead. The member currently throws a NotImplementedException. Will be " + + "removed in version 7.0.0.", true)] + public System.Nullable HasSupportForDistributedTransactions { get; set; } + [System.ObsoleteAttribute("Use TransportInfrastructure.TransactionMode == TransportTransactionMode.SendsAtom" + + "icWithReceive instead. The member currently throws a NotImplementedException. Wi" + + "ll be removed in version 7.0.0.", true)] + public bool HasSupportForMultiQueueNativeTransactions { get; set; } + public virtual bool RequiresConnectionString { get; } + public abstract NServiceBus.Transport.TransportInfrastructure Initialize(NServiceBus.Settings.SettingsHolder settings, string connectionString); + } + public abstract class TransportInfrastructure + { + protected TransportInfrastructure() { } + public abstract System.Collections.Generic.IEnumerable DeliveryConstraints { get; } + public abstract NServiceBus.Transport.OutboundRoutingPolicy OutboundRoutingPolicy { get; } + public bool RequireOutboxConsent { get; set; } + public abstract NServiceBus.TransportTransactionMode TransactionMode { get; } + public abstract NServiceBus.Routing.EndpointInstance BindToLocalEndpoint(NServiceBus.Routing.EndpointInstance instance); + public abstract NServiceBus.Transport.TransportReceiveInfrastructure ConfigureReceiveInfrastructure(); + public abstract NServiceBus.Transport.TransportSendInfrastructure ConfigureSendInfrastructure(); + public abstract NServiceBus.Transport.TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure(); + public virtual string MakeCanonicalForm(string transportAddress) { } + public virtual System.Threading.Tasks.Task Start() { } + public virtual System.Threading.Tasks.Task Stop() { } + public abstract string ToTransportAddress(NServiceBus.LogicalAddress logicalAddress); + } + public class TransportOperation + { + public TransportOperation(NServiceBus.Transport.OutgoingMessage message, NServiceBus.Routing.AddressTag addressTag, NServiceBus.Transport.DispatchConsistency requiredDispatchConsistency = 1, System.Collections.Generic.List deliveryConstraints = null) { } + public NServiceBus.Routing.AddressTag AddressTag { get; } + public System.Collections.Generic.List DeliveryConstraints { get; } + public NServiceBus.Transport.OutgoingMessage Message { get; } + public NServiceBus.Transport.DispatchConsistency RequiredDispatchConsistency { get; set; } + } + public class TransportOperations + { + public TransportOperations(params NServiceBus.Transport.TransportOperation[] transportOperations) { } + public System.Collections.Generic.List MulticastTransportOperations { get; } + public System.Collections.Generic.List UnicastTransportOperations { get; } + } + public class TransportReceiveInfrastructure + { + public TransportReceiveInfrastructure(System.Func messagePumpFactory, System.Func queueCreatorFactory, System.Func> preStartupCheck) { } + public System.Func MessagePumpFactory { get; } + public System.Func QueueCreatorFactory { get; } + } + public class TransportSendInfrastructure + { + public TransportSendInfrastructure(System.Func dispatcherFactory, System.Func> preStartupCheck) { } + public System.Func DispatcherFactory { get; } + } + public class TransportSubscriptionInfrastructure + { + public TransportSubscriptionInfrastructure(System.Func subscriptionManagerFactory) { } + } + public sealed class TransportTransaction : NServiceBus.Extensibility.ContextBag + { + public TransportTransaction() { } + } + public class UnicastTransportOperation : NServiceBus.Transport.IOutgoingTransportOperation + { + public UnicastTransportOperation(NServiceBus.Transport.OutgoingMessage message, string destination, NServiceBus.Transport.DispatchConsistency requiredDispatchConsistency = 1, System.Collections.Generic.List deliveryConstraints = null) { } + public System.Collections.Generic.List DeliveryConstraints { get; } + public string Destination { get; } + public NServiceBus.Transport.OutgoingMessage Message { get; } + public NServiceBus.Transport.DispatchConsistency RequiredDispatchConsistency { get; } + } +} +namespace NServiceBus.Transport.Msmq +{ + public class HeaderInfo + { + public HeaderInfo() { } + public string Key { get; set; } + public string Value { get; set; } + } +} +namespace NServiceBus.Transports +{ + [System.ObsoleteAttribute("No longer used, safe to remove. Will be removed in version 7.0.0.", true)] + public interface IAuditMessages { } + [System.ObsoleteAttribute("Use `NServiceBus.Transport.IDispatchMessages` instead. Will be removed in version" + + " 7.0.0.", true)] + public interface IDeferMessages + { + [System.ObsoleteAttribute("Use `NServiceBus.Transport.ICancelDeferredMessages` instead. Will be removed in v" + + "ersion 7.0.0.", true)] + void ClearDeferredMessages(string headerKey, string headerValue); + } + [System.ObsoleteAttribute("Use `NServiceBus.Transport.IPushMessages` instead. Will be removed in version 7.0" + + ".0.", true)] + public interface IDequeueMessages + { + void Init(NServiceBus.Address address, NServiceBus.Unicast.Transport.TransactionSettings transactionSettings, System.Func tryProcessMessage, System.Action endProcessMessage); + void Start(int maximumConcurrencyLevel); + void Stop(); + } + [System.ObsoleteAttribute("Use `NServiceBus.Transport.IDispatchMessages` instead. Will be removed in version" + + " 7.0.0.", true)] + public interface IPublishMessages { } + [System.ObsoleteAttribute("Use `NServiceBus.Transport.IDispatchMessages` instead. Will be removed in version" + + " 7.0.0.", true)] + public interface ISendMessages + { + void Send(NServiceBus.TransportMessage message, NServiceBus.Unicast.SendOptions sendOptions); + } + [System.ObsoleteAttribute("The namespace NServiceBus.Transports was renamed to NServiceBus.Transport. Use `N" + + "ServiceBus.Transport.TransportDefinition` instead. Will be removed in version 7." + + "0.0.", true)] + public abstract class TransportDefinition + { + protected TransportDefinition() { } + } +} +namespace NServiceBus.Transports.Msmq.Config +{ + [System.ObsoleteAttribute("No longer available, see the documentation for native sends for alternative solut" + + "ions. Will be removed in version 7.0.0.", true)] + public class MsmqSettings + { + public MsmqSettings() { } + } +} +namespace NServiceBus.Transports.Msmq +{ + [System.ObsoleteAttribute("No longer available, resolve an instance of IPushMessages from the container inst" + + "ead. Will be removed in version 7.0.0.", true)] + public class MsmqDequeueStrategy + { + public MsmqDequeueStrategy() { } + } + [System.ObsoleteAttribute("No longer available, see the documentation for native sends for alternative solut" + + "ions. Will be removed in version 7.0.0.", true)] + public class MsmqMessageSender + { + public MsmqMessageSender() { } + } + [System.ObsoleteAttribute("The msmq transaction is now available via the pipeline context. Will be removed i" + + "n version 7.0.0.", true)] + public class MsmqUnitOfWork + { + public MsmqUnitOfWork() { } + } +} +namespace NServiceBus.Unicast.Behaviors +{ + public class MessageHandler + { + [System.ObsoleteAttribute("Use `NServiceBus.Pipeline.MessageHandler(Action invocatio" + + "n, Type handlerType)` instead. Will be removed in version 7.0.0.", true)] + public MessageHandler() { } + [System.ObsoleteAttribute("Use `NServiceBus.Pipeline.MessageHandler.Invoke` instead. The member currently th" + + "rows a NotImplementedException. Will be removed in version 7.0.0.", true)] + public System.Action Invocation { get; set; } + } +} +namespace NServiceBus.Unicast +{ + public class static BuilderExtensions + { + public static void ForEach(this NServiceBus.ObjectBuilder.IBuilder builder, System.Action action) { } + } + [System.ObsoleteAttribute("No longer used, use the new callbacks api described in the version 6 upgrade guid" + + "e. Will be removed in version 7.0.0.", true)] + public class BusAsyncResult + { + public BusAsyncResult() { } + } + public class DeliveryMessageOptions + { + public DeliveryMessageOptions() { } + [System.ObsoleteAttribute("Use the ConsistencyGuarantee class instead. The member currently throws a NotImpl" + + "ementedException. Will be removed in version 7.0.0.", true)] + public bool EnlistInReceiveTransaction { get; set; } + [System.ObsoleteAttribute("Use context.TryGetDeliveryConstraint instead. The member curr" + + "ently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public System.Nullable NonDurable { get; set; } + [System.ObsoleteAttribute("Use context.TryGetDeliveryConstraint instead. The mem" + + "ber currently throws a NotImplementedException. Will be removed in version 7.0.0" + + ".", true)] + public System.Nullable TimeToBeReceived { get; set; } + } + [System.ObsoleteAttribute("Use `NServiceBus.UnicastBus.DeliveryMessageOptions` instead. Will be removed in v" + + "ersion 7.0.0.", true)] + public abstract class DeliveryOptions + { + protected DeliveryOptions() { } + [System.ObsoleteAttribute("Turn best practices check off using configuration.DisableFeature(). The member currently throws a NotImplementedException. Will be remov" + + "ed in version 7.0.0.", true)] + public bool EnforceMessagingBestPractices { get; set; } + [System.ObsoleteAttribute("Reply to address can be get/set using the `NServiceBus.ReplyToAddress` header. Th" + + "e member currently throws a NotImplementedException. Will be removed in version " + + "7.0.0.", true)] + public string ReplyToAddress { get; set; } + } + [System.ObsoleteAttribute("Not a public API. Use `MessageHandlerRegistry` instead. Will be removed in versio" + + "n 7.0.0.", true)] + public interface IMessageHandlerRegistry { } + [System.ObsoleteAttribute("Will be removed in version 7.0.0.", true)] + public class MessageContext + { + public MessageContext() { } + } + public class MessageEventArgs : System.EventArgs + { + public MessageEventArgs(object msg) { } + public object Message { get; } + } + public class MessageHandlerRegistry + { + [System.ObsoleteAttribute("Use `MessageHandlerRegistry.RegisterHandler(Type handlerType)` instead. The membe" + + "r currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void CacheMethodForHandler(System.Type handler, System.Type messageType) { } + public void Clear() { } + public System.Collections.Generic.List GetHandlersFor(System.Type messageType) { } + [System.ObsoleteAttribute("Use `MessageHandlerRegistry.GetHandlersFor(Type messageType)` instead. The member" + + " currently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public System.Collections.Generic.IEnumerable GetHandlerTypes(System.Type messageType) { } + public System.Collections.Generic.IEnumerable GetMessageTypes() { } + [System.ObsoleteAttribute("Use `MessageHandler.Invoke(object message, object context)` instead. The member c" + + "urrently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void InvokeHandle(object handler, object message) { } + [System.ObsoleteAttribute("Use `MessageHandler.Invoke(object message, object context)` instead. The member c" + + "urrently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public void InvokeTimeout(object handler, object state) { } + public void RegisterHandler(System.Type handlerType) { } + } + public class MessagesEventArgs : System.EventArgs + { + public MessagesEventArgs(object[] messages) { } + public object[] Messages { get; } + } + [System.ObsoleteAttribute("Use context.Intent to detect of the message is a event being published and use co" + + "ntext.MessageType to get the actual event type. Will be removed in version 7.0.0" + + ".", true)] + public class PublishOptions : NServiceBus.Unicast.DeliveryOptions + { + public PublishOptions() { } + } + [System.ObsoleteAttribute("Not used anymore, use the \'NServiceBus.MessageIntent\' header to detect if the mes" + + "sage is a reply. Will be removed in version 7.0.0.", true)] + public class ReplyOptions + { + public ReplyOptions() { } + } + [System.ObsoleteAttribute("Use `NServiceBus.UnicastBus.SendMessageOptions` instead. Will be removed in versi" + + "on 7.0.0.", true)] + public class SendOptions : NServiceBus.Unicast.DeliveryOptions + { + [System.ObsoleteAttribute("Use `SendMessageOptions(string)` instead. The member currently throws a NotImplem" + + "entedException. Will be removed in version 7.0.0.", true)] + public SendOptions(NServiceBus.Address destination) { } + [System.ObsoleteAttribute("Reply to address can be get/set using the `NServiceBus.CorrelationId` header. The" + + " member currently throws a NotImplementedException. Will be removed in version 7" + + ".0.0.", true)] + public string CorrelationId { get; set; } + [System.ObsoleteAttribute("Use `DelayDeliveryFor` instead. The member currently throws a NotImplementedExcep" + + "tion. Will be removed in version 7.0.0.", true)] + public System.Nullable DelayDeliveryWith { get; set; } + } + [System.ObsoleteAttribute("UnicastBus has been made internal. Use IEndpointInstance instead. Will be removed" + + " in version 7.0.0.", true)] + public class UnicastBus + { + public UnicastBus() { } + } +} +namespace NServiceBus.Unicast.Messages +{ + public class MessageMetadata + { + public MessageMetadata(System.Type messageType) { } + public MessageMetadata(System.Type messageType, System.Type[] messageHierarchy) { } + public System.Type[] MessageHierarchy { get; } + public System.Type MessageType { get; } + [System.ObsoleteAttribute("You can access Recoverable via the DeliveryConstraints collection on the outgoing" + + " context, the new constraint is called NonDurableDelivery. The member currently " + + "throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public bool Recoverable { get; } + [System.ObsoleteAttribute("You can access TTBR via the DeliveryConstraints collection on the outgoing contex" + + "t. The member currently throws a NotImplementedException. Will be removed in ver" + + "sion 7.0.0.", true)] + public System.TimeSpan TimeToBeReceived { get; } + } + public class MessageMetadataRegistry + { + public NServiceBus.Unicast.Messages.MessageMetadata GetMessageMetadata(System.Type messageType) { } + public NServiceBus.Unicast.Messages.MessageMetadata GetMessageMetadata(string messageTypeIdentifier) { } + } +} +namespace NServiceBus.Unicast.Queuing +{ + [System.ObsoleteAttribute("Use `QueueBindings` instead. Will be removed in version 7.0.0.", true)] + public interface IWantQueueCreated { } + public class QueueNotFoundException : System.Exception + { + public QueueNotFoundException() { } + [System.ObsoleteAttribute("Use `QueueNotFoundException(string queue, string message, Exception inner)` inste" + + "ad. The member currently throws a NotImplementedException. Will be removed in ve" + + "rsion 7.0.0.", true)] + public QueueNotFoundException(NServiceBus.Address queue, string message, System.Exception inner) { } + public QueueNotFoundException(string queue, string message, System.Exception inner) { } + protected QueueNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public string Queue { get; set; } + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + } +} +namespace NServiceBus.Unicast.Routing +{ + [System.ObsoleteAttribute("No longer used, safe to remove. Will be removed in version 7.0.0.", true)] + public class StaticMessageRouter + { + public StaticMessageRouter() { } + [System.ObsoleteAttribute("Use `config.AutoSubscribe().AutoSubscribePlainMessages()` instead. The member cur" + + "rently throws a NotImplementedException. Will be removed in version 7.0.0.", true)] + public bool SubscribeToPlainMessages { get; set; } + } +} +namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions +{ + public interface IInitializableSubscriptionStorage : NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions.ISubscriptionStorage + { + void Init(); + } + public interface ISubscriptionStorage + { + System.Threading.Tasks.Task> GetSubscriberAddressesForMessage(System.Collections.Generic.IEnumerable messageTypes, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Subscribe(NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, NServiceBus.Unicast.Subscriptions.MessageType messageType, NServiceBus.Extensibility.ContextBag context); + System.Threading.Tasks.Task Unsubscribe(NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, NServiceBus.Unicast.Subscriptions.MessageType messageType, NServiceBus.Extensibility.ContextBag context); + } + public class Subscriber + { + public Subscriber(string transportAddress, string endpoint) { } + public string Endpoint { get; } + public string TransportAddress { get; } + } +} +namespace NServiceBus.Unicast.Subscriptions +{ + public class MessageType + { + public MessageType(System.Type type) { } + public MessageType(string messageTypeString) { } + public MessageType(string typeName, string versionString) { } + public MessageType(string typeName, System.Version version) { } + public string TypeName { get; } + public System.Version Version { get; } + public bool Equals(NServiceBus.Unicast.Subscriptions.MessageType other) { } + public override bool Equals(object obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + [System.ObsoleteAttribute("No longer used, safe to remove. Will be removed in version 7.0.0.", true)] + public class SubscriptionEventArgs + { + public SubscriptionEventArgs() { } + } +} +namespace NServiceBus.Unicast.Transport +{ + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class static ControlMessage { } + public class static ControlMessageFactory + { + public static NServiceBus.Transport.OutgoingMessage Create(NServiceBus.MessageIntentEnum intent) { } + } + [System.ObsoleteAttribute("Use the pipeline to catch failures. Will be removed in version 7.0.0.", true)] + public class FailedMessageProcessingEventArgs : System.EventArgs + { + public FailedMessageProcessingEventArgs() { } + } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class FinishedMessageProcessingEventArgs : System.EventArgs + { + public FinishedMessageProcessingEventArgs() { } + } + [System.ObsoleteAttribute("Use `NServiceBus.Transport.IPushMessages` instead. Will be removed in version 7.0" + + ".0.", true)] + public interface ITransport { } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class StartedMessageProcessingEventArgs + { + public StartedMessageProcessingEventArgs() { } + } + [System.ObsoleteAttribute("Transaction settings is no longer available via this class. See obsoletes on indi" + + "vidual members for further details. Will be removed in version 7.0.0.", true)] + public class TransactionSettings + { + [System.ObsoleteAttribute("No longer used. Will be removed in version 7.0.0.", true)] + public TransactionSettings(bool isTransactional, System.TimeSpan transactionTimeout, System.Transactions.IsolationLevel isolationLevel, bool suppressDistributedTransactions, bool doNotWrapHandlersExecutionInATransactionScope) { } + [System.ObsoleteAttribute("DoNotWrapHandlersExecutionInATransactionScope is no longer used here. Use setting" + + "s.GetOrDefault(\'Transactions.DoNotWrapHandlersExecutionInATransactionScope" + + "\') instead. Will be removed in version 7.0.0.", true)] + public bool DoNotWrapHandlersExecutionInATransactionScope { get; set; } + [System.ObsoleteAttribute("Isolation level are now controlled explicitly for the transaction scope unit of w" + + "ork using config.UnitOfWork().WrapHandlersInATransactionScope(isolationlevel: X)" + + ". Will be removed in version 7.0.0.", true)] + public System.Transactions.IsolationLevel IsolationLevel { get; set; } + [System.ObsoleteAttribute("IsTransactional is no longer used here. Use `context.Settings.GetRequiredTransact" + + "ionModeForReceives() != Transactions.None` instead. Will be removed in version 7" + + ".0.0.", true)] + public bool IsTransactional { get; set; } + [System.ObsoleteAttribute("SuppressDistributedTransactions is no longer used here. Use `context.Settings.Get" + + "RequiredTransactionModeForReceives() != Transactions.TransactionScope` instead. " + + "Will be removed in version 7.0.0.", true)] + public bool SuppressDistributedTransactions { get; set; } + [System.ObsoleteAttribute("Timeouts are now controlled explicitly for the transaction scope unit of work usi" + + "ng config.UnitOfWork().WrapHandlersInATransactionScope(timeout: X). Will be remo" + + "ved in version 7.0.0.", true)] + public System.TimeSpan TransactionTimeout { get; set; } + } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class TransportMessageAvailableEventArgs + { + public TransportMessageAvailableEventArgs() { } + } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class TransportMessageReceivedEventArgs + { + public TransportMessageReceivedEventArgs() { } + } + [System.ObsoleteAttribute("No longer used, can safely be removed. Will be removed in version 7.0.0.", true)] + public class TransportReceiver + { + public TransportReceiver() { } + } +} +namespace NServiceBus.UnitOfWork +{ + public interface IManageUnitsOfWork + { + System.Threading.Tasks.Task Begin(); + System.Threading.Tasks.Task End(System.Exception ex = null); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/API/APIApprovals.cs b/src/NServiceBus.Core.Tests/API/APIApprovals.cs new file mode 100644 index 00000000000..db8cfa28b89 --- /dev/null +++ b/src/NServiceBus.Core.Tests/API/APIApprovals.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Core.Tests.API +{ + using System; + using System.IO; + using System.Linq; + using System.Runtime.CompilerServices; + using ApiApprover; + using ApprovalTests; + using Mono.Cecil; + using NUnit.Framework; + + [TestFixture] + public class APIApprovals + { + [Test] + [MethodImpl(MethodImplOptions.NoInlining)] + public void ApproveNServiceBus() + { + var assemblyPath = Path.GetFullPath(typeof(Endpoint).Assembly.Location); + var asm = AssemblyDefinition.ReadAssembly(assemblyPath); + var publicApi = Filter(PublicApiGenerator.CreatePublicApiForAssembly(asm)); + Approvals.Verify(publicApi); + } + + string Filter(string text) + { + return string.Join(Environment.NewLine, text.Split(new[] + { + Environment.NewLine + }, StringSplitOptions.RemoveEmptyEntries) + .Where(l => !l.StartsWith("[assembly: ReleaseDateAttribute(")) + .Where(l => !string.IsNullOrWhiteSpace(l)) + ); + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Address/AddressTests.cs b/src/NServiceBus.Core.Tests/Address/AddressTests.cs deleted file mode 100644 index 149342a509f..00000000000 --- a/src/NServiceBus.Core.Tests/Address/AddressTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Core.Tests -{ - using NUnit.Framework; - - [TestFixture] - public class AddressTests - { - [Test, Explicit("We can't run this test in CI because Address uses statics and setting this flag will make other tests fail. And the flag can't be unset!")] - public void HashCode_ignores_machine_name() - { - Address.IgnoreMachineName(); - - var address1 = new Address("MyEndpoint", "Server1"); - var address2 = new Address("MyEndpoint", "Server2"); - - Assert.AreEqual(address1.GetHashCode(), address2.GetHashCode()); - } - } -} diff --git a/src/NServiceBus.Core.Tests/App.config b/src/NServiceBus.Core.Tests/App.config index a4dd0fe9e50..639654e07f5 100644 --- a/src/NServiceBus.Core.Tests/App.config +++ b/src/NServiceBus.Core.Tests/App.config @@ -1,10 +1,10 @@ - + -
+
-
+
@@ -27,4 +27,13 @@ + + + + + + + + + diff --git a/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiApprover.cs b/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiApprover.cs new file mode 100644 index 00000000000..c8649256cc2 --- /dev/null +++ b/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiApprover.cs @@ -0,0 +1,43 @@ +using System.IO; +using ApprovalTests; +using ApprovalTests.Namers; +using Mono.Cecil; + +namespace ApiApprover +{ + public static class PublicApiApprover + { + public static void ApprovePublicApi(string assemblyPath) + { + var assemblyResolver = new DefaultAssemblyResolver(); + assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath)); + + var readSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")); + var asm = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters(ReadingMode.Deferred) + { + ReadSymbols = readSymbols, + AssemblyResolver = assemblyResolver, + }); + + var publicApi = PublicApiGenerator.CreatePublicApiForAssembly(asm); + var writer = new ApprovalTextWriter(publicApi, "cs"); + var approvalNamer = new AssemblyPathNamer(assemblyPath); + ApprovalTests.Approvals.Verify(writer, approvalNamer, ApprovalTests.Approvals.GetReporter()); + } + + private class AssemblyPathNamer : UnitTestFrameworkNamer + { + string name; + + public AssemblyPathNamer(string assemblyPath) + { + name = Path.GetFileNameWithoutExtension(assemblyPath); + } + + public override string Name + { + get { return name; } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiGenerator.cs b/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiGenerator.cs new file mode 100644 index 00000000000..2933106faa8 --- /dev/null +++ b/src/NServiceBus.Core.Tests/App_Packages/ApiApprover.3.0.1/PublicApiGenerator.cs @@ -0,0 +1,794 @@ +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CSharp; +using Mono.Cecil; +using Mono.Cecil.Rocks; +using TypeAttributes = System.Reflection.TypeAttributes; + +// ReSharper disable CheckNamespace +// ReSharper disable BitwiseOperatorOnEnumWithoutFlags +namespace ApiApprover +{ + public static class CecilEx + { + public static IEnumerable GetMembers(this TypeDefinition type) + { + return type.Fields.Cast() + .Concat(type.Methods) + .Concat(type.Properties) + .Concat(type.Events); + } + } + + + public static class PublicApiGenerator + { + // TODO: Assembly references? + // TODO: Better handle namespaces - using statements? - requires non-qualified type names + public static string CreatePublicApiForAssembly(AssemblyDefinition assembly) + { + return CreatePublicApiForAssembly(assembly, t => true, true); + } + + public static string CreatePublicApiForAssembly(AssemblyDefinition assembly, Func shouldIncludeType, bool shouldIncludeAssemblyAttributes) + { + var publicApiBuilder = new StringBuilder(); + var cgo = new CodeGeneratorOptions + { + BracingStyle = "C", + BlankLinesBetweenMembers = false, + VerbatimOrder = false + }; + + using (var provider = new CSharpCodeProvider()) + { + var compileUnit = new CodeCompileUnit(); + if (shouldIncludeAssemblyAttributes && assembly.HasCustomAttributes) + { + PopulateCustomAttributes(assembly, compileUnit.AssemblyCustomAttributes); + } + + var publicTypes = assembly.Modules.SelectMany(m => m.GetTypes()) + .Where(t => !t.IsNested && ShouldIncludeType(t) && shouldIncludeType(t)) + .OrderBy(t => t.FullName); + foreach (var publicType in publicTypes) + { + var @namespace = compileUnit.Namespaces.Cast() + .FirstOrDefault(n => n.Name == publicType.Namespace); + if (@namespace == null) + { + @namespace = new CodeNamespace(publicType.Namespace); + compileUnit.Namespaces.Add(@namespace); + } + + var typeDeclaration = CreateTypeDeclaration(publicType); + @namespace.Types.Add(typeDeclaration); + } + + using (var writer = new StringWriter()) + { + provider.GenerateCodeFromCompileUnit(compileUnit, writer, cgo); + var typeDeclarationText = NormaliseGeneratedCode(writer); + publicApiBuilder.AppendLine(typeDeclarationText); + } + } + return NormaliseLineEndings(publicApiBuilder.ToString().Trim()); + } + + private static string NormaliseLineEndings(string value) + { + return Regex.Replace(value, @"\r\n|\n\r|\r|\n", Environment.NewLine); + } + + private static bool IsDelegate(TypeDefinition publicType) + { + return publicType.BaseType != null && publicType.BaseType.FullName == "System.MulticastDelegate"; + } + + private static bool ShouldIncludeType(TypeDefinition t) + { + return (t.IsPublic || t.IsNestedPublic || t.IsNestedFamily) && !IsCompilerGenerated(t); + } + + private static bool ShouldIncludeMember(IMemberDefinition m) + { + return !IsCompilerGenerated(m) && !IsDotNetTypeMember(m) && !(m is FieldDefinition); + } + + private static bool IsCompilerGenerated(IMemberDefinition m) + { + return m.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute"); + } + + private static bool IsDotNetTypeMember(IMemberDefinition m) + { + if (m.DeclaringType == null || m.DeclaringType.FullName == null) + return false; + return m.DeclaringType.FullName.StartsWith("System") || m.DeclaringType.FullName.StartsWith("Microsoft"); + } + + static void AddMemberToTypeDeclaration(CodeTypeDeclaration typeDeclaration, IMemberDefinition memberInfo) + { + var methodDefinition = memberInfo as MethodDefinition; + if (methodDefinition != null) + { + if (methodDefinition.IsConstructor) + AddCtorToTypeDeclaration(typeDeclaration, methodDefinition); + else + AddMethodToTypeDeclaration(typeDeclaration, methodDefinition); + } + else if (memberInfo is PropertyDefinition) + { + AddPropertyToTypeDeclaration(typeDeclaration, (PropertyDefinition) memberInfo); + } + else if (memberInfo is EventDefinition) + { + typeDeclaration.Members.Add(GenerateEvent((EventDefinition)memberInfo)); + } + else if (memberInfo is FieldDefinition) + { + AddFieldToTypeDeclaration(typeDeclaration, (FieldDefinition) memberInfo); + } + } + + static string NormaliseGeneratedCode(StringWriter writer) + { + var gennedClass = writer.ToString(); + const string autoGeneratedHeader = @"^//-+\s*$.*^//-+\s*$"; + const string emptyGetSet = @"\s+{\s+get\s+{\s+}\s+set\s+{\s+}\s+}"; + const string emptyGet = @"\s+{\s+get\s+{\s+}\s+}"; + const string emptySet = @"\s+{\s+set\s+{\s+}\s+}"; + const string getSet = @"\s+{\s+get;\s+set;\s+}"; + const string get = @"\s+{\s+get;\s+}"; + const string set = @"\s+{\s+set;\s+}"; + gennedClass = Regex.Replace(gennedClass, autoGeneratedHeader, string.Empty, + RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline); + gennedClass = Regex.Replace(gennedClass, emptyGetSet, " { get; set; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, getSet, " { get; set; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, emptyGet, " { get; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, emptySet, " { set; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, get, " { get; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, set, " { set; }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, @"\s+{\s+}", " { }", RegexOptions.IgnorePatternWhitespace); + gennedClass = Regex.Replace(gennedClass, @"\)\s+;", ");", RegexOptions.IgnorePatternWhitespace); + return gennedClass; + } + + static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType) + { + if (IsDelegate(publicType)) + return CreateDelegateDeclaration(publicType); + + bool @static = false; + TypeAttributes attributes = 0; + if (publicType.IsPublic || publicType.IsNestedPublic) + attributes |= TypeAttributes.Public; + if (publicType.IsNestedFamily) + attributes |= TypeAttributes.NestedFamily; + if (publicType.IsSealed && !publicType.IsAbstract) + attributes |= TypeAttributes.Sealed; + else if (!publicType.IsSealed && publicType.IsAbstract && !publicType.IsInterface) + attributes |= TypeAttributes.Abstract; + else if (publicType.IsSealed && publicType.IsAbstract) + @static = true; + + // Static support is a hack. CodeDOM does support it, and this isn't + // correct C#, but it's good enough for our API outline + var name = publicType.Name; + + var index = name.IndexOf('`'); + if (index != -1) + name = name.Substring(0, index); + var declaration = new CodeTypeDeclaration(@static ? "static " + name : name) + { + CustomAttributes = CreateCustomAttributes(publicType), + // TypeAttributes must be specified before the IsXXX as they manipulate TypeAttributes! + TypeAttributes = attributes, + IsClass = publicType.IsClass, + IsEnum = publicType.IsEnum, + IsInterface = publicType.IsInterface, + IsStruct = publicType.IsValueType && !publicType.IsPrimitive && !publicType.IsEnum, + }; + + if (declaration.IsInterface && publicType.BaseType != null) + throw new NotImplementedException("Base types for interfaces needs testing"); + + PopulateGenericParameters(publicType, declaration.TypeParameters); + + if (publicType.BaseType != null && ShouldOutputBaseType(publicType)) + { + if (publicType.BaseType.FullName == "System.Enum") + { + var underlyingType = publicType.GetEnumUnderlyingType(); + if (underlyingType.FullName != "System.Int32") + declaration.BaseTypes.Add(CreateCodeTypeReference(underlyingType)); + } + else + declaration.BaseTypes.Add(CreateCodeTypeReference(publicType.BaseType)); + } + foreach (var @interface in publicType.Interfaces.OrderBy(i => i.FullName)) + declaration.BaseTypes.Add(CreateCodeTypeReference(@interface)); + + foreach (var memberInfo in publicType.GetMembers().Where(ShouldIncludeMember).OrderBy(m => m.Name)) + AddMemberToTypeDeclaration(declaration, memberInfo); + + // Fields should be in defined order for an enum + var fields = !publicType.IsEnum + ? publicType.Fields.OrderBy(f => f.Name) + : (IEnumerable)publicType.Fields; + foreach (var field in fields) + AddMemberToTypeDeclaration(declaration, field); + + foreach (var nestedType in publicType.NestedTypes.Where(ShouldIncludeType).OrderBy(t => t.FullName)) + { + var nestedTypeDeclaration = CreateTypeDeclaration(nestedType); + declaration.Members.Add(nestedTypeDeclaration); + } + + return declaration; + } + + private static CodeTypeDeclaration CreateDelegateDeclaration(TypeDefinition publicType) + { + var invokeMethod = publicType.Methods.Single(m => m.Name == "Invoke"); + var name = publicType.Name; + var index = name.IndexOf('`'); + if (index != -1) + name = name.Substring(0, index); + var declaration = new CodeTypeDelegate(name) + { + Attributes = MemberAttributes.Public, + CustomAttributes = CreateCustomAttributes(publicType), + ReturnType = CreateCodeTypeReference(invokeMethod.ReturnType), + }; + + // CodeDOM. No support. Return type attributes. + PopulateCustomAttributes(invokeMethod.MethodReturnType, declaration.CustomAttributes, type => ModifyCodeTypeReference(type, "return:")); + PopulateGenericParameters(publicType, declaration.TypeParameters); + PopulateMethodParameters(invokeMethod, declaration.Parameters); + + // Of course, CodeDOM doesn't support generic type parameters for delegates. Of course. + if (declaration.TypeParameters.Count > 0) + { + var parameterNames = from parameterType in declaration.TypeParameters.Cast() + select parameterType.Name; + declaration.Name = string.Format("{0}<{1}>", declaration.Name, string.Join(", ", parameterNames)); + } + + return declaration; + } + + private static bool ShouldOutputBaseType(TypeDefinition publicType) + { + return publicType.BaseType.FullName != "System.Object" && publicType.BaseType.FullName != "System.ValueType"; + } + + private static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters) + { + foreach (var parameter in publicType.GenericParameters) + { + if (parameter.HasCustomAttributes) + throw new NotImplementedException("Attributes on type parameters is not supported. And weird"); + + // A little hacky. Means we get "in" and "out" prefixed on any constraints, but it's either that + // or add it as a custom attribute, which looks even weirder + var name = parameter.Name; + if (parameter.IsCovariant) + name = "out " + name; + if (parameter.IsContravariant) + name = "in " + name; + + var typeParameter = new CodeTypeParameter(name) + { + HasConstructorConstraint = + parameter.HasDefaultConstructorConstraint && !parameter.HasNotNullableValueTypeConstraint + }; + if (parameter.HasNotNullableValueTypeConstraint) + typeParameter.Constraints.Add(" struct"); // Extra space is a hack! + if (parameter.HasReferenceTypeConstraint) + typeParameter.Constraints.Add(" class"); + foreach (var constraint in parameter.Constraints.Where(t => t.FullName != "System.ValueType")) + { + typeParameter.Constraints.Add(CreateCodeTypeReference(constraint.GetElementType())); + } + parameters.Add(typeParameter); + } + } + + private static CodeAttributeDeclarationCollection CreateCustomAttributes(ICustomAttributeProvider type) + { + var attributes = new CodeAttributeDeclarationCollection(); + PopulateCustomAttributes(type, attributes); + return attributes; + } + + private static void PopulateCustomAttributes(ICustomAttributeProvider type, + CodeAttributeDeclarationCollection attributes) + { + PopulateCustomAttributes(type, attributes, ctr => ctr); + } + + private static void PopulateCustomAttributes(ICustomAttributeProvider type, + CodeAttributeDeclarationCollection attributes, Func codeTypeModifier) + { + foreach (var customAttribute in type.CustomAttributes.Where(ShouldIncludeAttribute).OrderBy(a => a.AttributeType.FullName).ThenBy(a => ConvertAttrbuteToCode(codeTypeModifier, a))) + { + var attribute = GenerateCodeAttributeDeclaration(codeTypeModifier, customAttribute); + attributes.Add(attribute); + } + } + + private static CodeAttributeDeclaration GenerateCodeAttributeDeclaration(Func codeTypeModifier, CustomAttribute customAttribute) + { + var attribute = new CodeAttributeDeclaration(codeTypeModifier(CreateCodeTypeReference(customAttribute.AttributeType))); + foreach (var arg in customAttribute.ConstructorArguments) + { + attribute.Arguments.Add(new CodeAttributeArgument(CreateInitialiserExpression(arg))); + } + foreach (var field in customAttribute.Fields.OrderBy(f => f.Name)) + { + attribute.Arguments.Add(new CodeAttributeArgument(field.Name, CreateInitialiserExpression(field.Argument))); + } + foreach (var property in customAttribute.Properties.OrderBy(p => p.Name)) + { + attribute.Arguments.Add(new CodeAttributeArgument(property.Name, CreateInitialiserExpression(property.Argument))); + } + return attribute; + } + + // Litee: This method is used for additional sorting of custom attributes when multiple values are allowed + private static object ConvertAttrbuteToCode(Func codeTypeModifier, CustomAttribute customAttribute) + { + using (var provider = new CSharpCodeProvider()) + { + var cgo = new CodeGeneratorOptions + { + BracingStyle = "C", + BlankLinesBetweenMembers = false, + VerbatimOrder = false + }; + var attribute = GenerateCodeAttributeDeclaration(codeTypeModifier, customAttribute); + var declaration = new CodeTypeDeclaration("DummyClass") + { + CustomAttributes = new CodeAttributeDeclarationCollection(new[] { attribute }), + }; + using (var writer = new StringWriter()) + { + provider.GenerateCodeFromType(declaration, writer, cgo); + return writer.ToString(); + } + } + } + + static HashSet SkipAttributeNames = new HashSet + { + "System.CodeDom.Compiler.GeneratedCodeAttribute", + "System.ComponentModel.EditorBrowsableAttribute", + "System.Runtime.CompilerServices.AsyncStateMachineAttribute", + "System.Runtime.CompilerServices.CompilerGeneratedAttribute", + "System.Runtime.CompilerServices.CompilationRelaxationsAttribute", + "System.Runtime.CompilerServices.ExtensionAttribute", + "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute", + "System.Reflection.DefaultMemberAttribute", + "System.Diagnostics.DebuggableAttribute", + "System.Diagnostics.DebuggerNonUserCodeAttribute", + "System.Diagnostics.DebuggerStepThroughAttribute", + "System.Reflection.AssemblyCompanyAttribute", + "System.Reflection.AssemblyConfigurationAttribute", + "System.Reflection.AssemblyCopyrightAttribute", + "System.Reflection.AssemblyDescriptionAttribute", + "System.Reflection.AssemblyFileVersionAttribute", + "System.Reflection.AssemblyInformationalVersionAttribute", + "System.Reflection.AssemblyProductAttribute", + "System.Reflection.AssemblyTitleAttribute", + "System.Reflection.AssemblyTrademarkAttribute" + }; + + private static bool ShouldIncludeAttribute(CustomAttribute attribute) + { + return !SkipAttributeNames.Contains(attribute.AttributeType.FullName); + } + + private static CodeExpression CreateInitialiserExpression(CustomAttributeArgument attributeArgument) + { + if (attributeArgument.Value is CustomAttributeArgument) + { + return CreateInitialiserExpression((CustomAttributeArgument) attributeArgument.Value); + } + + if (attributeArgument.Value is CustomAttributeArgument[]) + { + var initialisers = from argument in (CustomAttributeArgument[]) attributeArgument.Value + select CreateInitialiserExpression(argument); + return new CodeArrayCreateExpression(CreateCodeTypeReference(attributeArgument.Type), initialisers.ToArray()); + } + + var type = attributeArgument.Type.Resolve(); + var value = attributeArgument.Value; + if (type.BaseType != null && type.BaseType.FullName == "System.Enum") + { + var originalValue = Convert.ToInt64(value); + if (type.CustomAttributes.Any(a => a.AttributeType.FullName == "System.FlagsAttribute")) + { + //var allFlags = from f in type.Fields + // where f.Constant != null + // let v = Convert.ToInt64(f.Constant) + // where v == 0 || (originalValue & v) != 0 + // select (CodeExpression)new CodeFieldReferenceExpression(typeExpression, f.Name); + //return allFlags.Aggregate((current, next) => new CodeBinaryOperatorExpression(current, CodeBinaryOperatorType.BitwiseOr, next)); + + // I'd rather use the above, as it's just using the CodeDOM, but it puts + // brackets around each CodeBinaryOperatorExpression + var flags = from f in type.Fields + where f.Constant != null + let v = Convert.ToInt64(f.Constant) + where v == 0 || (originalValue & v) != 0 + select type.FullName + "." + f.Name; + return new CodeSnippetExpression(flags.Aggregate((current, next) => current + " | " + next)); + } + + var allFlags = from f in type.Fields + where f.Constant != null + let v = Convert.ToInt64(f.Constant) + where v == originalValue + select new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(CreateCodeTypeReference(type)), f.Name); + return allFlags.FirstOrDefault(); + } + + if (type.FullName == "System.Type" && value is TypeReference) + { + return new CodeTypeOfExpression(CreateCodeTypeReference((TypeReference)value)); + } + + if (value is string) + { + // CodeDOM outputs a verbatim string. Any string with \n is treated as such, so normalise + // it to make it easier for comparisons + value = Regex.Replace((string)value, @"\n", "\\n"); + value = Regex.Replace((string)value, @"\r\n|\r\\n", "\\r\\n"); + } + + return new CodePrimitiveExpression(value); + } + + private static void AddCtorToTypeDeclaration(CodeTypeDeclaration typeDeclaration, MethodDefinition member) + { + if (member.IsAssembly || member.IsPrivate) + return; + + var method = new CodeConstructor + { + CustomAttributes = CreateCustomAttributes(member), + Name = member.Name, + Attributes = GetMethodAttributes(member) + }; + PopulateMethodParameters(member, method.Parameters); + + typeDeclaration.Members.Add(method); + } + + private static void AddMethodToTypeDeclaration(CodeTypeDeclaration typeDeclaration, MethodDefinition member) + { + if (member.IsAssembly || member.IsPrivate || member.IsSpecialName) + return; + + var returnType = CreateCodeTypeReference(member.ReturnType); + if (IsAsyncMethod(member)) + returnType = MakeAsync(returnType); + + var method = new CodeMemberMethod + { + Name = member.Name, + Attributes = GetMethodAttributes(member), + CustomAttributes = CreateCustomAttributes(member), + ReturnType = returnType, + }; + PopulateCustomAttributes(member.MethodReturnType, method.ReturnTypeCustomAttributes); + PopulateGenericParameters(member, method.TypeParameters); + PopulateMethodParameters(member, method.Parameters, IsExtensionMethod(member)); + + typeDeclaration.Members.Add(method); + } + + private static bool IsAsyncMethod(ICustomAttributeProvider method) + { + return method.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.AsyncStateMachineAttribute"); + } + + private static bool IsExtensionMethod(ICustomAttributeProvider method) + { + return method.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute"); + } + + private static void PopulateMethodParameters(IMethodSignature member, + CodeParameterDeclarationExpressionCollection parameters, bool isExtension = false) + { + foreach (var parameter in member.Parameters) + { + FieldDirection direction = 0; + if (parameter.IsOut) + direction |= FieldDirection.Out; + else if (parameter.ParameterType.IsByReference) + direction |= FieldDirection.Ref; + + var parameterType = parameter.ParameterType.IsByReference + ? parameter.ParameterType.GetElementType() + : parameter.ParameterType; + + var type = CreateCodeTypeReference(parameterType); + + if (isExtension) + { + type = ModifyCodeTypeReference(type, "this"); + isExtension = false; + } + + var name = parameter.HasConstant + ? string.Format("{0} = {1}", parameter.Name, FormatParameterConstant(parameter)) + : parameter.Name; + var expression = new CodeParameterDeclarationExpression(type, name) + { + Direction = direction, + CustomAttributes = CreateCustomAttributes(parameter) + }; + parameters.Add(expression); + } + } + + private static object FormatParameterConstant(IConstantProvider parameter) + { + return parameter.Constant is string ? string.Format("\"{0}\"", parameter.Constant) : (parameter.Constant ?? "null"); + } + + static MemberAttributes GetMethodAttributes(MethodDefinition method) + { + MemberAttributes access = 0; + if (method.IsFamily) + access = MemberAttributes.Family; + if (method.IsPublic) + access = MemberAttributes.Public; + if (method.IsAssembly) + access = MemberAttributes.Assembly; + if (method.IsFamilyAndAssembly) + access = MemberAttributes.FamilyAndAssembly; + if (method.IsFamilyOrAssembly) + access = MemberAttributes.FamilyOrAssembly; + + MemberAttributes scope = 0; + if (method.IsStatic) + scope |= MemberAttributes.Static; + if (method.IsFinal || !method.IsVirtual) + scope |= MemberAttributes.Final; + if (method.IsAbstract) + scope |= MemberAttributes.Abstract; + if (method.IsVirtual && !method.IsNewSlot) + scope |= MemberAttributes.Override; + + MemberAttributes vtable = 0; + if (IsHidingMethod(method)) + vtable = MemberAttributes.New; + + return access | scope | vtable; + } + + private static bool IsHidingMethod(MethodDefinition method) + { + var typeDefinition = method.DeclaringType; + + // If we're an interface, just try and find any method with the same signature + // in any of the interfaces that we implement + if (typeDefinition.IsInterface) + { + var interfaceMethods = from @interfaceReference in typeDefinition.Interfaces + let interfaceDefinition = @interfaceReference.Resolve() + where interfaceDefinition != null + select interfaceDefinition.Methods; + + return interfaceMethods.Any(ms => MetadataResolver.GetMethod(ms, method) != null); + } + + // If we're not an interface, find a base method that isn't virtual + return !method.IsVirtual && GetBaseTypes(typeDefinition).Any(d => MetadataResolver.GetMethod(d.Methods, method) != null); + } + + private static IEnumerable GetBaseTypes(TypeDefinition type) + { + var baseType = type.BaseType; + while (baseType != null) + { + var definition = baseType.Resolve(); + if (definition == null) + yield break; + yield return definition; + + baseType = baseType.DeclaringType; + } + } + + private static void AddPropertyToTypeDeclaration(CodeTypeDeclaration typeDeclaration, PropertyDefinition member) + { + var getterAttributes = member.GetMethod != null ? GetMethodAttributes(member.GetMethod) : 0; + var setterAttributes = member.SetMethod != null ? GetMethodAttributes(member.SetMethod) : 0; + + if (!HasVisiblePropertyMethod(getterAttributes) && !HasVisiblePropertyMethod(setterAttributes)) + return; + + var propertyAttributes = GetPropertyAttributes(getterAttributes, setterAttributes); + + var propertyType = member.PropertyType.IsGenericParameter + ? new CodeTypeReference(member.PropertyType.Name) + : CreateCodeTypeReference(member.PropertyType); + + var property = new CodeMemberProperty + { + Name = member.Name, + Type = propertyType, + Attributes = propertyAttributes, + CustomAttributes = CreateCustomAttributes(member), + HasGet = member.GetMethod != null && HasVisiblePropertyMethod(getterAttributes), + HasSet = member.SetMethod != null && HasVisiblePropertyMethod(setterAttributes) + }; + + // Here's a nice hack, because hey, guess what, the CodeDOM doesn't support + // attributes on getters or setters + if (member.GetMethod != null && member.GetMethod.HasCustomAttributes) + { + PopulateCustomAttributes(member.GetMethod, property.CustomAttributes, type => ModifyCodeTypeReference(type, "get:")); + } + if (member.SetMethod != null && member.SetMethod.HasCustomAttributes) + { + PopulateCustomAttributes(member.GetMethod, property.CustomAttributes, type => ModifyCodeTypeReference(type, "set:")); + } + + foreach (var parameter in member.Parameters) + { + property.Parameters.Add( + new CodeParameterDeclarationExpression(CreateCodeTypeReference(parameter.ParameterType), + parameter.Name)); + } + + // TODO: CodeDOM has no support for different access modifiers for getters and setters + // TODO: CodeDOM has no support for attributes on setters or getters - promote to property? + + typeDeclaration.Members.Add(property); + } + + private static MemberAttributes GetPropertyAttributes(MemberAttributes getterAttributes, MemberAttributes setterAttributes) + { + MemberAttributes access = 0; + var getterAccess = getterAttributes & MemberAttributes.AccessMask; + var setterAccess = setterAttributes & MemberAttributes.AccessMask; + if (getterAccess == MemberAttributes.Public || setterAccess == MemberAttributes.Public) + access = MemberAttributes.Public; + else if (getterAccess == MemberAttributes.Family || setterAccess == MemberAttributes.Family) + access = MemberAttributes.Family; + else if (getterAccess == MemberAttributes.FamilyAndAssembly || setterAccess == MemberAttributes.FamilyAndAssembly) + access = MemberAttributes.FamilyAndAssembly; + else if (getterAccess == MemberAttributes.FamilyOrAssembly || setterAccess == MemberAttributes.FamilyOrAssembly) + access = MemberAttributes.FamilyOrAssembly; + else if (getterAccess == MemberAttributes.Assembly || setterAccess == MemberAttributes.Assembly) + access = MemberAttributes.Assembly; + else if (getterAccess == MemberAttributes.Private || setterAccess == MemberAttributes.Private) + access = MemberAttributes.Private; + + // Scope should be the same for getter and setter. If one isn't specified, it'll be 0 + var getterScope = getterAttributes & MemberAttributes.ScopeMask; + var setterScope = setterAttributes & MemberAttributes.ScopeMask; + var scope = (MemberAttributes) Math.Max((int) getterScope, (int) setterScope); + + // Vtable should be the same for getter and setter. If one isn't specified, it'll be 0 + var getterVtable = getterAttributes & MemberAttributes.VTableMask; + var setterVtable = setterAttributes & MemberAttributes.VTableMask; + var vtable = (MemberAttributes) Math.Max((int) getterVtable, (int) setterVtable); + + return access | scope | vtable; + } + + private static bool HasVisiblePropertyMethod(MemberAttributes attributes) + { + var access = attributes & MemberAttributes.AccessMask; + return access == MemberAttributes.Public || access == MemberAttributes.Family || + access == MemberAttributes.FamilyOrAssembly; + } + + static CodeTypeMember GenerateEvent(EventDefinition eventDefinition) + { + var @event = new CodeMemberEvent + { + Name = eventDefinition.Name, + Attributes = MemberAttributes.Public | MemberAttributes.Final, + CustomAttributes = CreateCustomAttributes(eventDefinition), + Type = CreateCodeTypeReference(eventDefinition.EventType) + }; + + return @event; + } + + static void AddFieldToTypeDeclaration(CodeTypeDeclaration typeDeclaration, FieldDefinition memberInfo) + { + if (memberInfo.IsPrivate || memberInfo.IsAssembly || memberInfo.IsSpecialName) + return; + + MemberAttributes attributes = 0; + if (memberInfo.HasConstant) + attributes |= MemberAttributes.Const; + if (memberInfo.IsFamily) + attributes |= MemberAttributes.Family; + if (memberInfo.IsPublic) + attributes |= MemberAttributes.Public; + if (memberInfo.IsStatic && !memberInfo.HasConstant) + attributes |= MemberAttributes.Static; + + // TODO: Values for readonly fields are set in the ctor + var codeTypeReference = CreateCodeTypeReference(memberInfo.FieldType); + if (memberInfo.IsInitOnly) + codeTypeReference = MakeReadonly(codeTypeReference); + var field = new CodeMemberField(codeTypeReference, memberInfo.Name) + { + Attributes = attributes, + CustomAttributes = CreateCustomAttributes(memberInfo) + }; + + if (memberInfo.HasConstant) + field.InitExpression = new CodePrimitiveExpression(memberInfo.Constant); + + typeDeclaration.Members.Add(field); + } + + private static CodeTypeReference MakeReadonly(CodeTypeReference typeReference) + { + return ModifyCodeTypeReference(typeReference, "readonly"); + } + + private static CodeTypeReference MakeAsync(CodeTypeReference typeReference) + { + return ModifyCodeTypeReference(typeReference, "async"); + } + + private static CodeTypeReference ModifyCodeTypeReference(CodeTypeReference typeReference, string modifier) + { + using (var provider = new CSharpCodeProvider()) + return new CodeTypeReference(modifier + " " + provider.GetTypeOutput(typeReference)); + } + + private static CodeTypeReference CreateCodeTypeReference(TypeReference type) + { + var typeName = GetTypeName(type); + return new CodeTypeReference(typeName, CreateGenericArguments(type)); + } + + private static string GetTypeName(TypeReference type) + { + if (type.IsGenericParameter) + return type.Name; + + if (!type.IsNested) + { + return (!string.IsNullOrEmpty(type.Namespace) ? (type.Namespace + ".") : "") + type.Name; + } + + return GetTypeName(type.DeclaringType) + "." + type.Name; + } + + private static CodeTypeReference[] CreateGenericArguments(TypeReference type) + { + var genericInstance = type as IGenericInstance; + if (genericInstance == null) return null; + + var genericArguments = new List(); + foreach (var argument in genericInstance.GenericArguments) + { + genericArguments.Add(CreateCodeTypeReference(argument)); + } + return genericArguments.ToArray(); + } + } +} +// ReSharper restore BitwiseOperatorOnEnumWihtoutFlags +// ReSharper restore CheckNamespace diff --git a/src/NServiceBus.Core.Tests/ApprovalTestConfig.cs b/src/NServiceBus.Core.Tests/ApprovalTestConfig.cs new file mode 100644 index 00000000000..2c78f7b53da --- /dev/null +++ b/src/NServiceBus.Core.Tests/ApprovalTestConfig.cs @@ -0,0 +1,6 @@ +using ApprovalTests.Reporters; +#if(DEBUG) +[assembly: UseReporter(typeof(DiffReporter), typeof(AllFailingTestsClipboardReporter))] +#else +[assembly: UseReporter(typeof(DiffReporter))] +#endif \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ArgumentExceptionTests.cs b/src/NServiceBus.Core.Tests/ArgumentExceptionTests.cs new file mode 100644 index 00000000000..aacf7c3bf56 --- /dev/null +++ b/src/NServiceBus.Core.Tests/ArgumentExceptionTests.cs @@ -0,0 +1,165 @@ +namespace NServiceBus.Core.Tests +{ + using System; + using System.IO; + using System.Linq; + using Mono.Cecil; + using NUnit.Framework; + + [TestFixture] + public class ArgumentExceptionTests + { + [Test] + [Explicit] + public void WriteAllPublicMembersWithNoArgumentChecking() + { + var stringWriter = new StringWriter(); + var codeBase = typeof(Endpoint).Assembly.CodeBase; + var uri = new UriBuilder(codeBase); + var path = Uri.UnescapeDataString(uri.Path); + + var readerParameters = new ReaderParameters + { + ReadSymbols = true + }; + var module = ModuleDefinition.ReadModule(path, readerParameters); + foreach (var type in module.GetTypes()) + { + if (!type.IsPublic) + { + continue; + } + if (type.IsValueType) + { + continue; + } + if (type.IsInterface) + { + continue; + } + + if (type.BaseType != null) + { + if (type.BaseType.Name == "ConfigurationSection") + { + continue; + } + if (type.BaseType.Name == "ConfigurationElementCollection") + { + continue; + } + if (type.BaseType.Name == "ConfigurationElement") + { + continue; + } + } + + if (ContainsObsoleteAttribute(type)) + { + continue; + } + + foreach (var method in type.Methods) + { + if (!method.HasParameters) + { + continue; + } + if (method.Parameters.All(x => x.IsOut || x.IsReturnValue || x.HasDefault)) + { + continue; + } + + if (method.Name.StartsWith("set_") || method.Name.StartsWith("get_")) + { + continue; + } + if (method.Name.StartsWith("add_") || method.Name.StartsWith("remove_")) + { + continue; + } + if (method.Name.StartsWith("op_")) + { + continue; + } + if (ContainsObsoleteAttribute(method)) + { + continue; + } + if (!method.HasBody) + { + continue; + } + if (!method.IsPublic) + { + continue; + } + if (MethodCallSelf(method)) + { + continue; + } + if (!MethodContainsArgumentException(method)) + { + WriteMethod(method, stringWriter); + } + } + } + + var methods = stringWriter.ToString(); + + Assert.IsEmpty(methods, methods); + } + + bool MethodCallSelf(MethodDefinition method) + { + foreach (var instruction in method.Body.Instructions) + { + var methodReference = instruction.Operand as MethodReference; + if (methodReference == null) + { + continue; + } + if (methodReference.Name == method.Name) + { + if (methodReference.DeclaringType.Name == method.DeclaringType.Name) + { + return true; + } + if (method.DeclaringType.BaseType != null && methodReference.DeclaringType.Name == method.DeclaringType.BaseType.Name) + { + return true; + } + } + } + return false; + } + + static void WriteMethod(MethodDefinition method, TextWriter writer) + { + writer.WriteLine("\r\n" + method.DeclaringType.Name + "." + method.Name); + var instruction = method.Body.Instructions.FirstOrDefault(x => x.SequencePoint != null); + if (instruction != null) + { + writer.WriteLine("file://" + instruction.SequencePoint.Document.Url.Replace(@"\", "/")); + } + } + + static bool MethodContainsArgumentException(MethodDefinition method) + { + return method.Body.Instructions + .Select(instruction => instruction.Operand) + .OfType() + .Select(reference => reference.DeclaringType.Name) + .Any(name => + (name.Contains("Argument") && + name.Contains("Exception")) || name == "Guard"); + } + + public bool ContainsObsoleteAttribute(ICustomAttributeProvider attributeProvider) + { + return attributeProvider + .CustomAttributes + .Any(x => x.AttributeType.Name == "ObsoleteAttribute"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyLocation.cs b/src/NServiceBus.Core.Tests/AssemblyLocation.cs index c371e802a7d..4cc559c7a03 100644 --- a/src/NServiceBus.Core.Tests/AssemblyLocation.cs +++ b/src/NServiceBus.Core.Tests/AssemblyLocation.cs @@ -3,13 +3,8 @@ public static class AssemblyLocation { - public static string CurrentDirectory - { - get - { - return Path.GetDirectoryName(CurrentAssemblyPath); - } - } + public static string CurrentDirectory => Path.GetDirectoryName(CurrentAssemblyPath); + public static string CurrentAssemblyPath { get diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/AssemblyScannerTests.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/AssemblyScannerTests.cs index 1c6e4bf1cee..5b793fba998 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/AssemblyScannerTests.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/AssemblyScannerTests.cs @@ -1,18 +1,34 @@ namespace NServiceBus.Core.Tests.AssemblyScanner { using System; + using System.IO; + using System.Reflection; using Hosting.Helpers; using NUnit.Framework; [TestFixture] public class AssemblyScannerTests { + public static string GetTestAssemblyDirectory() + { + var directoryName = GetAssemblyDirectory(); + return Path.Combine(directoryName, "TestDlls"); + } + + public static string GetAssemblyDirectory() + { + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new UriBuilder(codeBase); + var path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } [Test] public void System_assemblies_should_be_excluded() { Assert.IsTrue(AssemblyScanner.IsRuntimeAssembly(typeof(string).Assembly.Location)); Assert.IsTrue(AssemblyScanner.IsRuntimeAssembly(typeof(Uri).Assembly.Location)); + Assert.IsTrue(AssemblyScanner.IsRuntimeAssembly(new AssemblyName("mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes"))); } [Test] @@ -20,5 +36,33 @@ public void Non_system_assemblies_should_be_included() { Assert.IsFalse(AssemblyScanner.IsRuntimeAssembly(GetType().Assembly.Location)); } + + [Test] + public void ReferencesNServiceBus_returns_true_for_indirect_reference() + { + var combine = Path.Combine(GetTestAssemblyDirectory(),"AssemblyWithNoDirectReference.dll"); + Assert.IsTrue(AssemblyScanner.ReferencesNServiceBus(combine)); + } + + [Test] + public void ReferencesNServiceBus_requires_binding_redirect() + { + var combine = Path.Combine(GetTestAssemblyDirectory(), "AssemblyWithRefToSN.dll"); + Assert.IsTrue(AssemblyScanner.ReferencesNServiceBus(combine)); + } + + [Test] + public void ReferencesNServiceBus_returns_true_for_direct_reference() + { + var combine = Path.Combine(GetTestAssemblyDirectory(), "AssemblyWithReference.dll"); + Assert.IsTrue(AssemblyScanner.ReferencesNServiceBus(combine)); + } + + [Test] + public void ReferencesNServiceBus_returns_false_for_no_reference() + { + var combine = Path.Combine(GetTestAssemblyDirectory(), "dotNet.dll"); + Assert.IsFalse(AssemblyScanner.ReferencesNServiceBus(combine)); + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_checking_image_type.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_checking_image_type.cs new file mode 100644 index 00000000000..d9d039056c4 --- /dev/null +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_checking_image_type.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Core.Tests.AssemblyScanner +{ + using System.IO; + using NUnit.Framework; + + [TestFixture] + public class When_checking_image_type + { + [Test] + public void roslyn_x86_image_type_correctly_detected() + { + var file = Path.Combine(AssemblyScannerTests.GetTestAssemblyDirectory(), "RoslynX86.dll"); + + var compilationMode = Image.GetCompilationMode(file); + + Assert.That(compilationMode, Is.EqualTo(Image.CompilationMode.CLRx86)); + } + + [Test] + public void roslyn_x64_image_type_correctly_detected() + { + var file = Path.Combine(AssemblyScannerTests.GetTestAssemblyDirectory(), "RoslynX64.dll"); + + var compilationMode = Image.GetCompilationMode(file); + + Assert.That(compilationMode, Is.EqualTo(Image.CompilationMode.CLRx64)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory__with_non_dot_net_dll_is_scanned.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory__with_non_dot_net_dll_is_scanned.cs deleted file mode 100644 index 8ac7e697e3f..00000000000 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory__with_non_dot_net_dll_is_scanned.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace NServiceBus.Core.Tests.AssemblyScanner -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using Hosting.Helpers; - using NUnit.Framework; - - [TestFixture] - public class When_directory__with_non_dot_net_dll_is_scanned - { - AssemblyScannerResults results; - List skippedFiles; - - [SetUp] - public void Context() - { - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new UriBuilder(codeBase); - var path = Uri.UnescapeDataString(uri.Path); - var directoryName = Path.GetDirectoryName(path); - - var testDllDirectory = Path.Combine(directoryName, "TestDlls"); - var assemblyScanner = new AssemblyScanner(testDllDirectory) - { - IncludeAppDomainAssemblies = false - }; - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); - - results = assemblyScanner - .GetScannableAssemblies(); - - skippedFiles = results.SkippedFiles; - } - - [Test] - public void non_dotnet_files_are_skipped() - { - var notProperDotNetDlls = new[] - { - "libzmq-v120-mt-3_2_3.dll", - "Tail.exe", - "some_random.dll", - "some_random.exe" - }; - - foreach (var notProperDll in notProperDotNetDlls) - { - var skippedFile = skippedFiles.FirstOrDefault(f => f.FilePath.Contains(notProperDll)); - - if (skippedFile == null) - { - throw new AssertionException(string.Format("Could not find skipped file matching {0}", notProperDll)); - } - - Assert.That(skippedFile.SkipReason, Contains.Substring("not a .NET assembly")); - } - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_handler_dll_is_scanned.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_handler_dll_is_scanned.cs index 4de89478e8f..4cd68a9f74a 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_handler_dll_is_scanned.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_handler_dll_is_scanned.cs @@ -7,31 +7,24 @@ [TestFixture] public class When_directory_with_handler_dll_is_scanned { - AssemblyScannerResults results; - - [SetUp] - public void Context() + [Test] + public void dll_with_message_handlers_gets_loaded() { var assemblyScanner = new AssemblyScanner(AssemblyLocation.CurrentDirectory) - { - IncludeAppDomainAssemblies = false - }; - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); + { + IncludeAppDomainAssemblies = false + }; - results = assemblyScanner + var results = assemblyScanner .GetScannableAssemblies(); - } - [Test] - public void dll_with_message_handlers_gets_loaded() - { var containsHandlers = "NServiceBus.Core.Tests"; //< assembly name, not file name var assembly = results.Assemblies .FirstOrDefault(a => a.GetName().Name.Contains(containsHandlers)); if (assembly == null) { - throw new AssertionException(string.Format("Could not find loaded assembly matching {0}", containsHandlers)); + throw new AssertionException($"Could not find loaded assembly matching {containsHandlers}"); } } } diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_no_reference_dlls_is_scanned.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_no_reference_dlls_is_scanned.cs index 9d53cb49cfb..8cfb1e22bf7 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_no_reference_dlls_is_scanned.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_no_reference_dlls_is_scanned.cs @@ -1,48 +1,31 @@ namespace NServiceBus.Core.Tests.AssemblyScanner { - using System; - using System.Collections.Generic; - using System.IO; using System.Linq; - using System.Reflection; using Hosting.Helpers; using NUnit.Framework; [TestFixture] public class When_directory_with_no_reference_dlls_is_scanned { - List skippedFiles; - - [SetUp] - public void Context() + [Test] + [Explicit("TODO: re-enable when we make message scanning lazy #1617")] + public void assemblies_without_nsb_reference_are_skipped() { - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new UriBuilder(codeBase); - var path = Uri.UnescapeDataString(uri.Path); - var directoryName = Path.GetDirectoryName(path); - - var testDllDirectory = Path.Combine(directoryName, "TestDlls"); - var assemblyScanner = new AssemblyScanner(testDllDirectory) - { - IncludeAppDomainAssemblies = false - }; - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); + var assemblyScanner = new AssemblyScanner(AssemblyScannerTests.GetTestAssemblyDirectory()) + { + IncludeAppDomainAssemblies = false + }; var results = assemblyScanner .GetScannableAssemblies(); - skippedFiles = results.SkippedFiles; - } + var skippedFiles = results.SkippedFiles; - [Test] - [Explicit("TODO: re-enable when we make message scanning lazy #1617")] - public void assemblies_without_nsb_reference_are_skipped() - { var skippedFile = skippedFiles.FirstOrDefault(f => f.FilePath.Contains("dotNet.dll")); if (skippedFile == null) { - throw new AssertionException(string.Format("Could not find skipped file matching {0}", "dotNet.dll")); + throw new AssertionException($"Could not find skipped file matching {"dotNet.dll"}"); } Assert.That(skippedFile.SkipReason, Contains.Substring("Assembly does not reference at least one of the must referenced assemblies")); diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_non_dot_net_dll_is_scanned.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_non_dot_net_dll_is_scanned.cs new file mode 100644 index 00000000000..76e13ddbfea --- /dev/null +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_directory_with_non_dot_net_dll_is_scanned.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.Core.Tests.AssemblyScanner +{ + using System.Linq; + using Hosting.Helpers; + using NUnit.Framework; + + [TestFixture] + public class When_directory_with_non_dot_net_dll_is_scanned + { + [Test] + public void non_dotnet_files_are_skipped() + { + var assemblyScanner = new AssemblyScanner(AssemblyScannerTests.GetTestAssemblyDirectory()) + { + IncludeAppDomainAssemblies = false + }; + + var results = assemblyScanner + .GetScannableAssemblies(); + + var skippedFiles = results.SkippedFiles; + + var notProperDotNetDlls = new[] + { + "libzmq-v120-mt-3_2_3.dll", + "Tail.exe", + "some_random.dll", + "some_random.exe" + }; + + foreach (var notProperDll in notProperDotNetDlls) + { + var skippedFile = skippedFiles.FirstOrDefault(f => f.FilePath.Contains(notProperDll)); + + if (skippedFile == null) + { + throw new AssertionException($"Could not find skipped file matching {notProperDll}"); + } + + Assert.That(skippedFile.SkipReason, Contains.Substring("not a .NET assembly")); + } + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_exclusion_predicate_is_used.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_exclusion_predicate_is_used.cs index 08589c06921..80abc7a9131 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_exclusion_predicate_is_used.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_exclusion_predicate_is_used.cs @@ -1,43 +1,27 @@ namespace NServiceBus.Core.Tests.AssemblyScanner { - using System; using System.Collections.Generic; - using System.IO; using System.Linq; - using System.Reflection; using Hosting.Helpers; using NUnit.Framework; [TestFixture] public class When_exclusion_predicate_is_used { - AssemblyScannerResults results; - List skippedFiles; - - [SetUp] - public void Context() - { - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new UriBuilder(codeBase); - var path = Uri.UnescapeDataString(uri.Path); - var directoryName = Path.GetDirectoryName(path); - - var testDllDirectory = Path.Combine(directoryName, "TestDlls"); - results = new AssemblyScanner(testDllDirectory) - { - AssembliesToSkip = new List - { - "dotNet.dll" - } - } - .GetScannableAssemblies(); - - skippedFiles = results.SkippedFiles; - } [Test] public void no_files_explicitly_excluded_are_returned() { + var results = new AssemblyScanner(AssemblyScannerTests.GetTestAssemblyDirectory()) + { + AssembliesToSkip = new List + { + "dotNet.dll" + } + } + .GetScannableAssemblies(); + + var skippedFiles = results.SkippedFiles; var explicitlySkippedDll = skippedFiles.FirstOrDefault(s => s.FilePath.Contains("dotNet.dll")); Assert.That(explicitlySkippedDll, Is.Not.Null); diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_inclusion_predicate_is_used.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_inclusion_predicate_is_used.cs deleted file mode 100644 index ddc301b83d1..00000000000 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_inclusion_predicate_is_used.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus.Core.Tests.AssemblyScanner -{ - using System.Collections.Generic; - using System.Linq; - using Hosting.Helpers; - using NUnit.Framework; - - [TestFixture] - public class When_inclusion_predicate_is_used - { - AssemblyScannerResults results; - - [SetUp] - public void Context() - { - var assemblyScanner = new AssemblyScanner(AssemblyLocation.CurrentDirectory) - { - AssembliesToInclude = new List - { - "NServiceBus.Core.Tests.dll" - } - }; - results = assemblyScanner - .GetScannableAssemblies(); - } - - [Test] - public void only_files_explicitly_included_are_returned() - { - Assert.That(results.Assemblies, Has.Count.EqualTo(1)); - Assert.That(results.Assemblies.Single().GetName().Name, Is.EqualTo("NServiceBus.Core.Tests")); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_directory_with_nested_directories.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_directory_with_nested_directories.cs new file mode 100644 index 00000000000..ae60fdc34af --- /dev/null +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_directory_with_nested_directories.cs @@ -0,0 +1,38 @@ +namespace NServiceBus.Core.Tests.AssemblyScanner +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Config; + using NUnit.Framework; + + [TestFixture] + public class When_scanning_directory_with_nested_directories + { + [Test] + public void Should_not_scan_nested_directories_by_default() + { + var endpointConfiguration = new EndpointConfiguration("myendpoint"); + endpointConfiguration.ExcludeTypes(typeof(When_using_initialization_with_non_default_ctor.FeatureWithInitialization)); + endpointConfiguration.Build(); + + var scanedTypes = endpointConfiguration.Settings.Get>("TypesToScan"); + var foundTypeFromNestedAssembly = scanedTypes.Any(x => x.Name == "NestedClass"); + Assert.False(foundTypeFromNestedAssembly, "Was expected not to scan nested assemblies, but nested assembly was scanned."); + } + + + [Test] + public void Should_scan_nested_directories_if_requested() + { + var endpointConfiguration = new EndpointConfiguration("myendpoint"); + endpointConfiguration.ScanAssembliesInNestedDirectories(); + endpointConfiguration.ExcludeTypes(typeof(When_using_initialization_with_non_default_ctor.FeatureWithInitialization)); + endpointConfiguration.Build(); + + var scanedTypes = endpointConfiguration.Settings.Get>("TypesToScan"); + var foundTypeFromNestedAssembly = scanedTypes.Any(x => x.Name == "NestedClass"); + Assert.True(foundTypeFromNestedAssembly, "Was expected to scan nested assemblies, but nested assembly were not scanned."); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_for_dlls_only.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_for_dlls_only.cs index 39861922375..b6e24384f8f 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_for_dlls_only.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_for_dlls_only.cs @@ -8,9 +8,7 @@ namespace NServiceBus.Core.Tests.AssemblyScanner [TestFixture] public class When_scanning_for_dlls_only { - static readonly string BaseDirectoryToScan = Path.Combine(Path.GetTempPath(), "empty"); - - AssemblyScannerResults results; + static string BaseDirectoryToScan = Path.Combine(Path.GetTempPath(), "empty"); [SetUp] public void Context() @@ -19,13 +17,6 @@ public void Context() var dllFilePath = Path.Combine(BaseDirectoryToScan, "NotAProper.exe"); File.WriteAllText(dllFilePath, "This is not a proper EXE"); - - results = new AssemblyScanner(BaseDirectoryToScan) - { - IncludeAppDomainAssemblies = true, - IncludeExesInScan = false, - } - .GetScannableAssemblies(); } [TearDown] @@ -38,6 +29,12 @@ public void TearDown() [Test] public void should_not_find_assembly_in_sub_directory() { + var results = new AssemblyScanner(BaseDirectoryToScan) + { + IncludeAppDomainAssemblies = true, + IncludeExesInScan = false, + }.GetScannableAssemblies(); + var allEncounteredFileNames = results.Assemblies .Where(x => !x.IsDynamic) diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_top_level_only.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_top_level_only.cs index 22511df7b93..00100235145 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_top_level_only.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_scanning_top_level_only.cs @@ -11,8 +11,6 @@ public class When_scanning_top_level_only static string baseDirectoryToScan = Path.Combine(Path.GetTempPath(), "empty"); static string someSubDirectory = Path.Combine(baseDirectoryToScan, "subDir"); - AssemblyScannerResults results; - [SetUp] public void Context() { @@ -21,13 +19,6 @@ public void Context() var dllFilePath = Path.Combine(someSubDirectory, "NotAProper.dll"); File.WriteAllText(dllFilePath, "This is not a proper DLL"); - - results = new AssemblyScanner(baseDirectoryToScan) - { - IncludeAppDomainAssemblies = true, - ScanNestedDirectories = false - } - .GetScannableAssemblies(); } [TearDown] @@ -40,6 +31,13 @@ public void TearDown() [Test] public void should_not_find_assembly_in_sub_directory() { + var results = new AssemblyScanner(baseDirectoryToScan) + { + IncludeAppDomainAssemblies = true, + ScanNestedDirectories = false + } + .GetScannableAssemblies(); + var allEncounteredFileNames = results.Assemblies .Where(x => !x.IsDynamic) diff --git a/src/NServiceBus.Core.Tests/AssemblyScanner/When_told_to_scan_app_domain.cs b/src/NServiceBus.Core.Tests/AssemblyScanner/When_told_to_scan_app_domain.cs index 434517d7421..c9200b147e1 100644 --- a/src/NServiceBus.Core.Tests/AssemblyScanner/When_told_to_scan_app_domain.cs +++ b/src/NServiceBus.Core.Tests/AssemblyScanner/When_told_to_scan_app_domain.cs @@ -2,37 +2,32 @@ namespace NServiceBus.Core.Tests.AssemblyScanner { using System.IO; using System.Linq; + using System.Threading.Tasks; using Hosting.Helpers; using NUnit.Framework; [TestFixture] public class When_told_to_scan_app_domain { - AssemblyScannerResults results; - - [SetUp] - public void Context() - { - var someDir = Path.Combine(Path.GetTempPath(), "empty"); - Directory.CreateDirectory(someDir); - - results = new AssemblyScanner(someDir) - { - IncludeAppDomainAssemblies = true, - } - .GetScannableAssemblies(); - } - class SomeHandlerThatEnsuresThatWeKeepReferencingNsbCore : IHandleMessages { - public void Handle(string message) + public Task Handle(string message, IMessageHandlerContext context) { + return TaskEx.CompletedTask; } } [Test] public void Should_use_AppDomain_Assemblies_if_flagged() { + var someDir = Path.Combine(Path.GetTempPath(), "empty"); + Directory.CreateDirectory(someDir); + + var results = new AssemblyScanner(someDir) + { + IncludeAppDomainAssemblies = true, + }.GetScannableAssemblies(); + var collection = results.Assemblies.Select(a => a.GetName().Name).ToArray(); CollectionAssert.Contains(collection, "NServiceBus.Core.Tests"); diff --git a/src/NServiceBus.Core.Tests/Audit/AuditConfigReaderTests.cs b/src/NServiceBus.Core.Tests/Audit/AuditConfigReaderTests.cs new file mode 100644 index 00000000000..ee36c862e93 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Audit/AuditConfigReaderTests.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Core.Tests.Audit +{ + using NUnit.Framework; + using Settings; + + public class AuditConfigReaderTests + { + [Test] + public void ShouldUseExplicitValueInSettingsIfPresent() + { + string address; + var settingsHolder = new SettingsHolder(); + + settingsHolder.Set(new AuditConfigReader.Result + { + Address = "myAuditQueue" + }); + + Assert.True(AuditConfigReader.TryGetAuditQueueAddress(settingsHolder, out address)); + Assert.AreEqual("myAuditQueue", address); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/AutoSubscriptionContext.cs b/src/NServiceBus.Core.Tests/AutomaticSubscriptions/AutoSubscriptionContext.cs deleted file mode 100644 index 42e1935ef1b..00000000000 --- a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/AutoSubscriptionContext.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NServiceBus.Core.Tests.AutomaticSubscriptions -{ - using System; - using System.Collections.Generic; - using NServiceBus.AutomaticSubscriptions; - using NUnit.Framework; - using Unicast; - using Unicast.Routing; - using Conventions = NServiceBus.Conventions; - - public class AutoSubscriptionContext - { - [SetUp] - public void SetUp() - { - autoSubscriptionStrategy = new AutoSubscriptionStrategy - { - HandlerRegistry = new MessageHandlerRegistry(new Conventions()), - MessageRouter = new StaticMessageRouter(KnownMessageTypes()), - Conventions = new Conventions() - }; - } - - protected virtual IEnumerable KnownMessageTypes() - { - return new List(); - } - - protected void RegisterMessageHandlerType() - { - ((MessageHandlerRegistry) autoSubscriptionStrategy.HandlerRegistry).RegisterHandler(typeof(T)); - } - - protected void RegisterMessageType(Address address) - { - autoSubscriptionStrategy.MessageRouter.RegisterMessageRoute(typeof(T), address); - } - - internal AutoSubscriptionStrategy autoSubscriptionStrategy; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_autosubscribing_a_saga_that_handles_a_superclass_event.cs b/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_autosubscribing_a_saga_that_handles_a_superclass_event.cs deleted file mode 100644 index 518d10abf85..00000000000 --- a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_autosubscribing_a_saga_that_handles_a_superclass_event.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace NServiceBus.Core.Tests.AutomaticSubscriptions -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NUnit.Framework; - using Saga; - - [TestFixture] - public class When_autoSubscribing_a_saga_that_handles_a_superclass_event : AutoSubscriptionContext - { - protected override IEnumerable KnownMessageTypes() - { - return new[] { typeof(EventWithParent), typeof(EventMessageBase) }; - } - [Test] - public void Should_autoSubscribe_the_saga_messageHandler() - { - - var eventEndpointAddress = new Address("PublisherAddress", "localhost"); - - RegisterMessageType(eventEndpointAddress); - RegisterMessageHandlerType(); - - Assert.True(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Saga event handler should be subscribed"); - } - - public class MySagaThatReactsOnASuperClassEvent : Saga, IAmStartedByMessages - { - public void Handle(EventMessageBase message) - { - throw new NotImplementedException(); - } - - - public class MySagaData : ContainSagaData - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - public class EventMessageBase : IEvent - { - - } - - - public class EventWithParent : EventMessageBase - { - - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_containing_a_saga.cs b/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_containing_a_saga.cs deleted file mode 100644 index b65dcadbc5b..00000000000 --- a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_containing_a_saga.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.Core.Tests.AutomaticSubscriptions -{ - using System; - using System.Linq; - using NUnit.Framework; - using Saga; - using Unicast.Tests.Contexts; - - [TestFixture] - public class When_starting_an_endpoint_containing_a_saga : AutoSubscriptionContext - { - [Test] - public void Should_autoSubscribe_the_saga_messageHandler_by_default() - { - var eventEndpointAddress = new Address("PublisherAddress", "localhost"); - - RegisterMessageType(eventEndpointAddress); - RegisterMessageHandlerType(); - - Assert.True(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Events only handled by sagas should be auto subscribed"); - } - - [Test] - public void Should_not_autoSubscribe_the_saga_messageHandler_on_demand() - { - var eventEndpointAddress = new Address("PublisherAddress", "localhost"); - - RegisterMessageType(eventEndpointAddress); - RegisterMessageHandlerType(); - - autoSubscriptionStrategy.DoNotAutoSubscribeSagas = true; - - Assert.False(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Events only handled by sagas should not be auto subscribed on demand"); - } - - public class MySaga : Saga, IAmStartedByMessages - { - public void Handle(EventMessage message) - { - throw new NotImplementedException(); - } - - public class MySagaData : ContainSagaData - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_with_autosubscribe_turned_on.cs b/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_with_autosubscribe_turned_on.cs deleted file mode 100644 index 7c41cf43e5f..00000000000 --- a/src/NServiceBus.Core.Tests/AutomaticSubscriptions/When_starting_an_endpoint_with_autosubscribe_turned_on.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.Core.Tests.AutomaticSubscriptions -{ - using System.Linq; - using NUnit.Framework; - using Unicast.Routing; - using Unicast.Tests.Contexts; - - [TestFixture] - public class When_starting_an_endpoint_with_autoSubscribe_turned_on : AutoSubscriptionContext - { - [Test] - public void Should_not_autoSubscribe_commands() - { - - var commandEndpointAddress = new Address("CommandEndpoint", "localhost"); - - RegisterMessageType(commandEndpointAddress); - RegisterMessageHandlerType(); - - - Assert.False(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Commands should not be auto subscribed"); - } - - - [Test] - public void Should_not_autoSubscribe_messages_by_default() - { - var endpointAddress = new Address("MyEndpoint", "localhost"); - - RegisterMessageType(endpointAddress); - RegisterMessageHandlerType(); - - Assert.False(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Plain messages should not be auto subscribed by default"); - } - - [Test] - public void Should_not_autoSubscribe_messages_unless_asked_to_by_the_users() - { - var endpointAddress = new Address("MyEndpoint", "localhost"); - - autoSubscriptionStrategy.SubscribePlainMessages = true; - - RegisterMessageType(endpointAddress); - RegisterMessageHandlerType(); - - Assert.True(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Plain messages should be auto subscribed if users wants them to be"); - } - - - [Test] - public void Should_autoSubscribe_messages_without_routing_if_configured_to_do_so() - { - autoSubscriptionStrategy.MessageRouter = new StaticMessageRouter(new[] { typeof(EventMessage) }); - - RegisterMessageHandlerType(); - - autoSubscriptionStrategy.DoNotRequireExplicitRouting = true; - Assert.True(autoSubscriptionStrategy.GetEventsToSubscribe().Any(), "Events without routing should be auto subscribed if asked for"); - } - - class MyMessage : IMessage - { - - } - - class MyMessageHandler : IHandleMessages - { - public void Handle(MyMessage message) - { - throw new System.NotImplementedException(); - } - } - - - public class EventMessageHandler : IHandleMessages - { - public void Handle(EventMessage message) - { - throw new System.NotImplementedException(); - } - } - public class CommandMessageHandler : IHandleMessages - { - public void Handle(CommandMessage message) - { - throw new System.NotImplementedException(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Causation/AttachCausationHeadersBehaviorTests.cs b/src/NServiceBus.Core.Tests/Causation/AttachCausationHeadersBehaviorTests.cs new file mode 100644 index 00000000000..7560c46214c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Causation/AttachCausationHeadersBehaviorTests.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.Core.Tests.Causation +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Testing; + using Transport; + + [TestFixture] + public class AttachCausationHeadersBehaviorTests + { + [Test] + public async Task Should_set_the_conversation_id_to_new_guid_when_not_sent_from_handler() + { + var behavior = new AttachCausationHeadersBehavior(); + var context = InitializeContext(); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreNotEqual(Guid.Empty.ToString(), context.Headers[Headers.ConversationId]); + } + + [Test] + public async Task Should_set_the_conversation_id_to_conversation_id_of_incoming_message() + { + var incomingConversationId = Guid.NewGuid().ToString(); + + var behavior = new AttachCausationHeadersBehavior(); + var context = InitializeContext(); + + var transportMessage = new IncomingMessage("xyz", new Dictionary + { + {Headers.ConversationId, incomingConversationId} + }, new byte[0]); + context.Extensions.Set(transportMessage); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(incomingConversationId, context.Headers[Headers.ConversationId]); + } + + [Test, Ignore("Will be refactored to use a explicit override via options instead and not rely on the header being set")] + public async Task Should_not_override_a_conversation_id_specified_by_the_user() + { + var userConversationId = Guid.NewGuid().ToString(); + + var behavior = new AttachCausationHeadersBehavior(); + var context = InitializeContext(); + + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(userConversationId, context.Headers[Headers.ConversationId]); + } + + [Test] + public async Task Should_set_the_related_to_header_with_the_id_of_the_current_message() + { + var behavior = new AttachCausationHeadersBehavior(); + var context = InitializeContext(); + + context.Extensions.Set(new IncomingMessage("the message id", new Dictionary(), new byte[0])); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("the message id", context.Headers[Headers.RelatedTo]); + } + + static IOutgoingPhysicalMessageContext InitializeContext() + { + return new TestableOutgoingPhysicalMessageContext(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Config/When_finding_assemblies_to_scan_with_expressions.cs b/src/NServiceBus.Core.Tests/Config/When_finding_assemblies_to_scan_with_expressions.cs deleted file mode 100644 index 1f04f35bedc..00000000000 --- a/src/NServiceBus.Core.Tests/Config/When_finding_assemblies_to_scan_with_expressions.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NServiceBus.Core.Tests.Config -{ - using System.Linq; - using NUnit.Framework; - - [TestFixture] - public class When_finding_assemblies_to_scan_with_expressions - { - [Test] - public void Should_exclude_by_name_without_extension() - { - var found = AllAssemblies.Except("rhino.mocks").ToArray(); - - Assert.False( - found.Any(a => a.GetName().Name == "Rhino.Mocks")); - } - - [Test] - public void Should_exclude_by_name_without_extension_and_with_upper_letters() - { - var found = AllAssemblies.Except("Rhino.Mocks").ToArray(); - - Assert.False( - found.Any(a => a.GetName().Name == "Rhino.Mocks")); - } - - [Test] - public void Should_exclude_by_name_with_extension() - { - var found = AllAssemblies.Except("rhino.mocks.dll").ToArray(); - - Assert.False( - found.Any(a => a.GetName().Name == "Rhino.Mocks")); - } - - [Test] - public void Should_exclude_by_name_with_expression() - { - var found = AllAssemblies.Except("rhino.").ToArray(); - - Assert.False( - found.Any(a => a.GetName().Name == "Rhino.Mocks")); - } - - [Test] - public void Should_include_fsharp_by_expression() - { - var found = AllAssemblies - .Matching("TestAssembly.").ToArray(); - - Assert.True(found.Any(a => a.GetName().Name == "TestAssembly")); - } - - [Test] - public void Should_include_fsharp_using_And() - { - var found = AllAssemblies - .Matching("foo.bar") - .And("TestAssembly.") - .ToArray(); - - Assert.True(found.Any(a => a.GetName().Name == "TestAssembly")); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Config/When_loading_types.cs b/src/NServiceBus.Core.Tests/Config/When_loading_types.cs deleted file mode 100644 index 1595fc7ebde..00000000000 --- a/src/NServiceBus.Core.Tests/Config/When_loading_types.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Core.Tests.Config -{ - using System.Linq; - using System.Reflection; - using NUnit.Framework; - - [TestFixture] - public class When_loading_types - { - - [Test] - public void Should_always_include_the_core_nservicebus_types() - { - var builder = new BusConfiguration(); - - builder.AssembliesToScan(Assembly.GetExecutingAssembly()); - - Assert.True(builder.BuildConfiguration().Settings.GetAvailableTypes().Any(a => a.Assembly.GetName().Name.Equals("NServiceBus.Core"))); - } - } - - public class TestClass - { - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Config/When_no_custom_configuration_source_is_specified.cs b/src/NServiceBus.Core.Tests/Config/When_no_custom_configuration_source_is_specified.cs index d3783f7db03..e8db37cd72c 100644 --- a/src/NServiceBus.Core.Tests/Config/When_no_custom_configuration_source_is_specified.cs +++ b/src/NServiceBus.Core.Tests/Config/When_no_custom_configuration_source_is_specified.cs @@ -1,17 +1,54 @@ namespace NServiceBus.Core.Tests.Config { + using System.Threading.Tasks; + using NServiceBus.Features; + using Settings; using NUnit.Framework; [TestFixture] public class When_no_custom_configuration_source_is_specified { [Test] - public void The_default_configuration_source_should_be_default() + public async Task The_default_configuration_source_should_be_default() { - var config = new BusConfiguration().BuildConfiguration(); + var config = new EndpointConfiguration("myendpoint"); - - Assert.AreEqual(config.Settings.GetConfigSection().TestSetting, "test"); + config.SendOnly(); + config.TypesToScanInternal(new[] { typeof(ConfigSectionValidatorFeature) }); + config.DisableFeature(); + config.EnableFeature(); + + var endpoint = await Endpoint.Start(config); + await endpoint.Stop(); + } + + class ConfigSectionValidatorFeature : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(() => new ValidatorTask(context.Settings)); + } + + class ValidatorTask : FeatureStartupTask + { + ReadOnlySettings settings; + + public ValidatorTask(ReadOnlySettings settings) + { + this.settings = settings; + } + + protected override Task OnStart(IMessageSession session) + { + Assert.AreEqual(settings.GetConfigSection().TestSetting, "test"); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Config/When_scanning_assemblies.cs b/src/NServiceBus.Core.Tests/Config/When_scanning_assemblies.cs index 5a9b2d58d3c..53e113d2b10 100644 --- a/src/NServiceBus.Core.Tests/Config/When_scanning_assemblies.cs +++ b/src/NServiceBus.Core.Tests/Config/When_scanning_assemblies.cs @@ -4,7 +4,7 @@ namespace NServiceBus.Core.Tests.Config using System.Collections.Generic; using System.Linq; using System.Reflection; - using NServiceBus.Hosting.Helpers; + using Hosting.Helpers; using NUnit.Framework; [TestFixture] @@ -19,33 +19,30 @@ public void SetUp() .ToList(); } - [Test, Ignore] + [Test, Ignore("Does not work")] public void Should_for_our_code_exclude_everything_but_NServiceBus_by_default() { - CollectionAssert.AreEquivalent(new String[0], - foundAssemblies.Where(a => !a.FullName.StartsWith("NServiceBus") && !a.FullName.StartsWith("Obsolete") - // FSharp is used as an example external assembly in other tests that is not excluded by default - && !a.FullName.StartsWith("TestAssembly")).ToArray()); + CollectionAssert.AreEquivalent(new string[0], + foundAssemblies.Where(a => !a.FullName.StartsWith("NServiceBus") && !a.FullName.StartsWith("Obsolete"))); } [Test] public void Should_exclude_system_assemblies() { - CollectionAssert.AreEquivalent(new String[0], + CollectionAssert.AreEquivalent(new string[0], foundAssemblies.Where(a => a.FullName.StartsWith("System")).ToArray()); } [Test] public void Should_exclude_nhibernate_assemblies() { - CollectionAssert.AreEquivalent(new String[0], + CollectionAssert.AreEquivalent(new string[0], foundAssemblies.Where(a => a.FullName.ToLower().StartsWith("nhibernate")).ToArray()); } IEnumerable GetAssembliesInDirectory(string path, params string[] assembliesToSkip) { var assemblyScanner = new AssemblyScanner(path); - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); if (assembliesToSkip != null) { assemblyScanner.AssembliesToSkip = assembliesToSkip.ToList(); diff --git a/src/NServiceBus.Core.Tests/Config/When_users_override_the_configuration_source.cs b/src/NServiceBus.Core.Tests/Config/When_users_override_the_configuration_source.cs index ac2452bd833..67e0288e308 100644 --- a/src/NServiceBus.Core.Tests/Config/When_users_override_the_configuration_source.cs +++ b/src/NServiceBus.Core.Tests/Config/When_users_override_the_configuration_source.cs @@ -1,39 +1,58 @@ namespace NServiceBus.Core.Tests.Config { - using System; + using System.Threading.Tasks; using NServiceBus.Config.ConfigurationSource; + using NServiceBus.Features; + using Settings; using NUnit.Framework; [TestFixture] public class When_users_override_the_configuration_source { - IConfigurationSource userConfigurationSource; - - Configure config; - - [SetUp] - public void SetUp() + [Test] + public async Task NService_bus_should_resolve_configuration_from_that_source() { - userConfigurationSource = new UserConfigurationSource(); + var builder = new EndpointConfiguration("myendpoint"); - var builder = new BusConfiguration(); - builder.TypesToScan(new Type[] - { - }); - builder.CustomConfigurationSource(userConfigurationSource); + builder.SendOnly(); + builder.TypesToScanInternal(new[] { typeof(ConfigSectionValidatorFeature) }); + builder.DisableFeature(); + builder.EnableFeature(); + builder.CustomConfigurationSource(new UserConfigurationSource()); - config = builder.BuildConfiguration(); + var endpoint = await Endpoint.Start(builder); + await endpoint.Stop(); } - - [Test] - public void NService_bus_should_resolve_configuration_from_that_source() + class ConfigSectionValidatorFeature : Feature { - var section = config.Settings.GetConfigSection(); + protected internal override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(new ValidatorTask(context.Settings)); + } - Assert.AreEqual(section.TestSetting,"TestValue"); - } + class ValidatorTask : FeatureStartupTask + { + ReadOnlySettings settings; + public ValidatorTask(ReadOnlySettings settings) + { + this.settings = settings; + } + + protected override Task OnStart(IMessageSession session) + { + var section = settings.GetConfigSection(); + Assert.AreEqual(section.TestSetting, "TestValue"); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + } + } } public class UserConfigurationSource : IConfigurationSource diff --git a/src/NServiceBus.Core.Tests/Config/When_using_initialization.cs b/src/NServiceBus.Core.Tests/Config/When_using_initialization.cs new file mode 100644 index 00000000000..5808e1149ea --- /dev/null +++ b/src/NServiceBus.Core.Tests/Config/When_using_initialization.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Core.Tests.Config +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class When_using_initialization_with_non_default_ctor + { + [Test] + public void Should_throw_meaningful_exception() + { + var builder = new EndpointConfiguration("myendpoint"); + + builder.TypesToScanInternal(new[] { typeof(FeatureWithInitialization) }); + + var ae = Assert.Throws(() => builder.Build()); + var expected = $"Unable to create the type '{nameof(FeatureWithInitialization)}'. Types implementing '{nameof(INeedInitialization)}' must have a public parameterless (default) constructor."; + Assert.AreEqual(expected, ae.Message); + } + + public class FeatureWithInitialization : INeedInitialization + { + public FeatureWithInitialization(string arg) + { + // Note: this ctor will cause the builder to throw an exception. + // If using assembly scanning in tests, ensure to exclude this type by using: + // endpointConfiguration.ExcludeTypes(typeof(When_using_initialization_with_non_default_ctor.FeatureWithInitialization)); + } + + public void Customize(EndpointConfiguration configuration) + { + } + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ContextBagTests.cs b/src/NServiceBus.Core.Tests/ContextBagTests.cs new file mode 100644 index 00000000000..fdff79fb577 --- /dev/null +++ b/src/NServiceBus.Core.Tests/ContextBagTests.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.Core.Tests +{ + using Extensibility; + using NUnit.Framework; + + [TestFixture] + public class ContextBagTests + { + [Test] + public void ShouldAllowMonkeyPatching() + { + var contextBag = new ContextBag(); + + contextBag.Set("MonkeyPatch", "some string"); + + string theValue; + + ((ReadOnlyContextBag) contextBag).TryGet("MonkeyPatch", out theValue); + Assert.AreEqual("some string", theValue); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Conventions/MessageConventionSpecs.cs b/src/NServiceBus.Core.Tests/Conventions/MessageConventionSpecs.cs deleted file mode 100644 index 6bbafcfe78c..00000000000 --- a/src/NServiceBus.Core.Tests/Conventions/MessageConventionSpecs.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.Core.Tests.Conventions -{ - namespace NServiceBus.Config.UnitTests - { - using NUnit.Framework; - using Conventions = global::NServiceBus.Conventions; - - [TestFixture] - public class When_applying_message_conventions_to_messages : MessageConventionTestBase - { - [Test] - public void Should_cache_the_message_convention() - { - var timesCalled = 0; - conventions = new Conventions - { - IsMessageTypeAction= t => - { - timesCalled++; - return false; - } - }; - - conventions.IsMessageType(GetType()); - Assert.AreEqual(1, timesCalled); - - conventions.IsMessageType(GetType()); - Assert.AreEqual(1, timesCalled); - } - } - - [TestFixture] - public class When_applying_message_conventions_to_events:MessageConventionTestBase - { - [Test] - public void Should_cache_the_message_convention() - { - var timesCalled = 0; - conventions = new Conventions - { - IsEventTypeAction = t => - { - timesCalled++; - return false; - } - }; - - conventions.IsEventType(GetType()); - Assert.AreEqual(1, timesCalled); - - conventions.IsEventType(GetType()); - Assert.AreEqual(1, timesCalled); - } - } - - [TestFixture] - public class When_applying_message_conventions_to_commands : MessageConventionTestBase - { - [Test] - public void Should_cache_the_message_convention() - { - var timesCalled = 0; - conventions = new Conventions - { - IsCommandTypeAction = t => - { - timesCalled++; - return false; - } - }; - - conventions.IsCommandType(GetType()); - Assert.AreEqual(1, timesCalled); - - conventions.IsCommandType(GetType()); - Assert.AreEqual(1, timesCalled); - } - } - } -} - -namespace NServiceBus.Core.Tests.Conventions.NServiceBus.Config.UnitTests -{ - using Conventions = global::NServiceBus.Conventions; - - public class MessageConventionTestBase - { - protected Conventions conventions; - } -} diff --git a/src/NServiceBus.Core.Tests/ConventionsTests.cs b/src/NServiceBus.Core.Tests/ConventionsTests.cs index ffa30f9a6a1..37b3e7449c1 100644 --- a/src/NServiceBus.Core.Tests/ConventionsTests.cs +++ b/src/NServiceBus.Core.Tests/ConventionsTests.cs @@ -1,47 +1,15 @@ namespace NServiceBus.Core.Tests { - using System; using NUnit.Framework; [TestFixture] public class ConventionsTests { - [Test] - public void Should_use_TimeToBeReceived_from_bottom_of_tree() - { - var conventions = new NServiceBus.Conventions(); - var timeToBeReceivedAction = conventions.GetTimeToBeReceived(typeof(InheritedClassWithAttribute)); - Assert.AreEqual(TimeSpan.FromSeconds(2), timeToBeReceivedAction); - } - - [Test] - public void Should_use_inherited_TimeToBeReceived() - { - var conventions = new NServiceBus.Conventions(); - var timeToBeReceivedAction = conventions.GetTimeToBeReceived(typeof(InheritedClassWithNoAttribute)); - Assert.AreEqual(TimeSpan.FromSeconds(1), timeToBeReceivedAction); - } - - [TimeToBeReceivedAttribute("00:00:01")] - class BaseClass - { - } - - [TimeToBeReceivedAttribute("00:00:02")] - class InheritedClassWithAttribute : BaseClass - { - - } - - class InheritedClassWithNoAttribute : BaseClass - { - - } - + [Test] public void IsMessageType_should_return_false_for_unknown_type() { - var conventions = new NServiceBus.Conventions(); + var conventions = new Conventions(); Assert.IsFalse(conventions.IsMessageType(typeof(NoAMessage))); } @@ -53,7 +21,7 @@ public class NoAMessage [Test] public void IsMessageType_should_return_true_for_IMessage() { - var conventions = new NServiceBus.Conventions(); + var conventions = new Conventions(); Assert.IsTrue(conventions.IsMessageType(typeof(MyMessage))); } @@ -65,7 +33,7 @@ public class MyMessage : IMessage [Test] public void IsMessageType_should_return_true_for_ICommand() { - var conventions = new NServiceBus.Conventions(); + var conventions = new Conventions(); Assert.IsTrue(conventions.IsMessageType(typeof(MyCommand))); } @@ -77,7 +45,7 @@ public class MyCommand : ICommand [Test] public void IsMessageType_should_return_true_for_IEvent() { - var conventions = new NServiceBus.Conventions(); + var conventions = new Conventions(); Assert.IsTrue(conventions.IsMessageType(typeof(MyEvent))); } @@ -89,7 +57,7 @@ public class MyEvent : IEvent [Test] public void IsMessageType_should_return_true_for_systemMessage() { - var conventions = new NServiceBus.Conventions(); + var conventions = new Conventions(); conventions.AddSystemMessagesConventions(type => type == typeof(MySystemMessage)); Assert.IsTrue(conventions.IsMessageType(typeof(MySystemMessage))); } @@ -106,52 +74,32 @@ public class When_using_a_greedy_convention_that_overlaps_with_NServiceBus [Test] public void IsCommandType_should_return_false_for_NServiceBus_types() { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsCommandTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly + IsCommandTypeAction = t => t.Assembly == typeof(Conventions).Assembly }; - Assert.IsFalse(conventions.IsCommandType(typeof(NServiceBus.Conventions))); - } - - [Test] - public void IsExpressMessageType_should_return_false_for_NServiceBus_types() - { - var conventions = new NServiceBus.Conventions - { - IsExpressMessageAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly - }; - Assert.IsFalse(conventions.IsExpressMessageType(typeof(NServiceBus.Conventions))); + Assert.IsFalse(conventions.IsCommandType(typeof(Conventions))); } + [Test] public void IsMessageType_should_return_false_for_NServiceBus_types() { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsMessageTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly + IsMessageTypeAction = t => t.Assembly == typeof(Conventions).Assembly }; - Assert.IsFalse(conventions.IsMessageType(typeof(NServiceBus.Conventions))); + Assert.IsFalse(conventions.IsMessageType(typeof(Conventions))); } [Test] public void IsEventType_should_return_false_for_NServiceBus_types() { - var conventions = new NServiceBus.Conventions - { - IsEventTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly - }; - Assert.IsFalse(conventions.IsEventType(typeof(NServiceBus.Conventions))); - } - - [Test] - public void IsExpressType_should_return_true_for_matching_type() - { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsExpressMessageAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly || - t == typeof(MyConventionExpress) + IsEventTypeAction = t => t.Assembly == typeof(Conventions).Assembly }; - Assert.IsTrue(conventions.IsExpressMessageType(typeof(MyConventionExpress))); + Assert.IsFalse(conventions.IsEventType(typeof(Conventions))); } public class MyConventionExpress @@ -161,9 +109,9 @@ public class MyConventionExpress [Test] public void IsCommandType_should_return_true_for_matching_type() { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsCommandTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly || + IsCommandTypeAction = t => t.Assembly == typeof(Conventions).Assembly || t == typeof(MyConventionCommand) }; Assert.IsTrue(conventions.IsCommandType(typeof(MyConventionCommand))); @@ -176,9 +124,9 @@ public class MyConventionCommand [Test] public void IsMessageType_should_return_true_for_matching_type() { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsMessageTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly || + IsMessageTypeAction = t => t.Assembly == typeof(Conventions).Assembly || t == typeof(MyConventionMessage) }; Assert.IsTrue(conventions.IsMessageType(typeof(MyConventionMessage))); @@ -191,9 +139,9 @@ public class MyConventionMessage [Test] public void IsEventType_should_return_true_for_matching_type() { - var conventions = new NServiceBus.Conventions + var conventions = new Conventions { - IsEventTypeAction = t => t.Assembly == typeof(NServiceBus.Conventions).Assembly || + IsEventTypeAction = t => t.Assembly == typeof(Conventions).Assembly || t == typeof(MyConventionEvent) }; Assert.IsTrue(conventions.IsEventType(typeof(MyConventionEvent))); diff --git a/src/NServiceBus.Core.Tests/Correlation/CorrelationContextExtensionsTests.cs b/src/NServiceBus.Core.Tests/Correlation/CorrelationContextExtensionsTests.cs new file mode 100644 index 00000000000..b0605d58345 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Correlation/CorrelationContextExtensionsTests.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.Core.Tests.Correlation +{ + using NUnit.Framework; + + [TestFixture] + public class CorrelationContextExtensionsTests + { + [Test] + public void SendOptions_GetCorrelationId_Should_Return_Configured_CorrelationId() + { + const string expectedCorrelationId = "custom correlation id"; + var options = new SendOptions(); + options.SetCorrelationId(expectedCorrelationId); + + var correlationId = options.GetCorrelationId(); + + Assert.AreEqual(expectedCorrelationId, correlationId); + } + + [Test] + public void SendOptions_GetCorrelationId_Should_Return_Null_When_No_CorrelationId_Configured() + { + var options = new SendOptions(); + + var correlationId = options.GetCorrelationId(); + + Assert.IsNull(correlationId); + } + + [Test] + public void ReplyOptions_GetCorrelationId_Should_Return_Configured_CorrelationId() + { + const string expectedCorrelationId = "custom correlation id"; + var options = new ReplyOptions(); + options.SetCorrelationId(expectedCorrelationId); + + var correlationId = options.GetCorrelationId(); + + Assert.AreEqual(expectedCorrelationId, correlationId); + } + + [Test] + public void ReplyOptions_GetCorrelationId_Should_Return_Null_When_No_CorrelationId_Configured() + { + var options = new ReplyOptions(); + + var correlationId = options.GetCorrelationId(); + + Assert.IsNull(correlationId); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DataBus/FileShare/AcceptanceTests.cs b/src/NServiceBus.Core.Tests/DataBus/FileShare/AcceptanceTests.cs index 6b0bda9ba8f..ae01b9dfaf8 100644 --- a/src/NServiceBus.Core.Tests/DataBus/FileShare/AcceptanceTests.cs +++ b/src/NServiceBus.Core.Tests/DataBus/FileShare/AcceptanceTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; - using NServiceBus.DataBus; using NUnit.Framework; [TestFixture] @@ -17,58 +16,60 @@ public void SetUp() } FileShareDataBusImplementation dataBus; - readonly string basePath = Path.GetTempPath(); + string basePath = Path.GetTempPath(); - string Put(string content, TimeSpan timeToLive) + async Task Put(string content, TimeSpan timeToLive) { var byteArray = Encoding.ASCII.GetBytes(content); using (var stream = new MemoryStream(byteArray)) { - return dataBus.Put(stream, timeToLive); + return await dataBus.Put(stream, timeToLive); } } [Test] - public void Should_handle_be_able_to_read_stored_values() + public async Task Should_handle_be_able_to_read_stored_values() { const string content = "Test"; - var key = Put(content, TimeSpan.MaxValue); - using (var stream = dataBus.Get(key)) + var key = await Put(content, TimeSpan.MaxValue); + using (var stream = await dataBus.Get(key)) + using (var streamReader = new StreamReader(stream)) { - Assert.AreEqual(new StreamReader(stream).ReadToEnd(), content); + Assert.AreEqual(await streamReader.ReadToEndAsync(), content); } } [Test] - public void Should_handle_be_able_to_read_stored_values_concurrently() + public async Task Should_handle_be_able_to_read_stored_values_concurrently() { const string content = "Test"; - var key = Put(content, TimeSpan.MaxValue); + var key = await Put(content, TimeSpan.MaxValue); - Parallel.For(0, 10, i => + Parallel.For(0, 10, async i => { - using (var stream = dataBus.Get(key)) + using (var stream = await dataBus.Get(key)) + using (var streamReader = new StreamReader(stream)) { - Assert.AreEqual(new StreamReader(stream).ReadToEnd(), content); + Assert.AreEqual(await streamReader.ReadToEndAsync(), content); } }); } [Test] - public void Should_handle_max_ttl() + public async Task Should_handle_max_ttl() { - Put("Test", TimeSpan.MaxValue); + await Put("Test", TimeSpan.MaxValue); Assert.True(Directory.Exists(Path.Combine(basePath, DateTime.MaxValue.ToString("yyyy-MM-dd_HH")))); } [Test] - public void Should_honor_the_ttl_limit() + public async Task Should_honor_the_ttl_limit() { dataBus.MaxMessageTimeToLive = TimeSpan.FromDays(1); - Put("Test", TimeSpan.MaxValue); + await Put("Test", TimeSpan.MaxValue); Assert.True(Directory.Exists(Path.Combine(basePath, DateTime.Now.AddDays(1).ToString("yyyy-MM-dd_HH")))); } } diff --git a/src/NServiceBus.Core.Tests/DataBus/InMemoryDataBus.cs b/src/NServiceBus.Core.Tests/DataBus/InMemoryDataBus.cs index 131ece04a95..9c7b81e489f 100644 --- a/src/NServiceBus.Core.Tests/DataBus/InMemoryDataBus.cs +++ b/src/NServiceBus.Core.Tests/DataBus/InMemoryDataBus.cs @@ -3,20 +3,21 @@ using System; using System.Collections.Generic; using System.IO; + using System.Threading.Tasks; class InMemoryDataBus : IDataBus { - readonly IDictionary storage = new Dictionary(); + IDictionary storage = new Dictionary(); /// /// Gets a data item from the bus. /// /// The key to look for. /// The data . - public Stream Get(string key) + public Task Get(string key) { lock (storage) - return new MemoryStream(storage[key].Data); + return Task.FromResult((Stream)new MemoryStream(storage[key].Data)); } /// @@ -24,7 +25,7 @@ public Stream Get(string key) /// /// A create containing the data to be sent on the databus. /// The time to be received specified on the message type. TimeSpan.MaxValue is the default. - public string Put(Stream stream, TimeSpan timeToBeReceived) + public Task Put(Stream stream, TimeSpan timeToBeReceived) { var key = Guid.NewGuid().ToString(); @@ -37,15 +38,16 @@ public string Put(Stream stream, TimeSpan timeToBeReceived) Data = data, ExpireAt = DateTime.Now + timeToBeReceived }); - return key; + return Task.FromResult(key); } /// /// Called when the bus starts up to allow the data bus to active background tasks. /// - public void Start() + public Task Start() { //no-op + return TaskEx.CompletedTask; } //used for test purposes diff --git a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_incoming_messages.cs b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_incoming_messages.cs index cfeb861a72e..9609c878034 100644 --- a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_incoming_messages.cs +++ b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_incoming_messages.cs @@ -4,40 +4,50 @@ namespace NServiceBus.Core.Tests.DataBus using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - using Rhino.Mocks; + using System.Threading.Tasks; + using NServiceBus.DataBus; + using NServiceBus.Pipeline; using Unicast.Messages; + using NUnit.Framework; [TestFixture] - class When_applying_the_databus_message_mutator_to_incoming_messages : on_the_bus + class When_applying_the_databus_message_mutator_to_incoming_messages { [Test] - public void Incoming_databus_properties_should_be_hydrated() + public async Task Incoming_databus_properties_should_be_hydrated() { var propertyKey = Guid.NewGuid().ToString(); var databusKey = Guid.NewGuid().ToString(); - var message = new LogicalMessage(null, new MessageWithDataBusProperty + var message = new LogicalMessage(new MessageMetadata(typeof(MessageWithDataBusProperty)), new MessageWithDataBusProperty { DataBusProperty = new DataBusProperty("not used in this test") { Key = propertyKey } - }, new Dictionary { { "NServiceBus.DataBus." + propertyKey, databusKey } }, null); - - + }); + + var fakeDatabus = new FakeDataBus(); + var receiveBehavior = new DataBusReceiveBehavior(fakeDatabus, new DefaultDataBusSerializer(), new Conventions()); + using (var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, "test"); stream.Position = 0; - dataBus.Stub(s => s.Get(databusKey)).Return(stream); + fakeDatabus.StreamsToReturn[databusKey] = stream; - receiveBehavior.Invoke(new IncomingContext(null, null) - { - IncomingLogicalMessage = message - }, () => { }); + await receiveBehavior.Invoke( + new IncomingLogicalMessageContext( + message, + "messageId", + "replyToAddress", + new Dictionary + { + {"NServiceBus.DataBus." + propertyKey, databusKey} + }, + null), + ctx => TaskEx.CompletedTask); } var instance = (MessageWithDataBusProperty)message.Instance; @@ -45,5 +55,25 @@ public void Incoming_databus_properties_should_be_hydrated() Assert.AreEqual(instance.DataBusProperty.Value, "test"); } + class FakeDataBus : IDataBus + { + public Dictionary StreamsToReturn = new Dictionary(); + + public Task Get(string key) + { + return Task.FromResult(StreamsToReturn[key]); + } + + public Task Put(Stream stream, TimeSpan timeToBeReceived) + { + throw new NotImplementedException(); + } + + public Task Start() + { + throw new NotImplementedException(); + } + } + } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_null_properties.cs b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_null_properties.cs index 21da24d7513..4a377d9c558 100644 --- a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_null_properties.cs +++ b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_null_properties.cs @@ -1,31 +1,29 @@ namespace NServiceBus.Core.Tests.DataBus { - using System; - using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; - using NServiceBus.Pipeline.Contexts; + using System.Threading.Tasks; + using NServiceBus.Pipeline; using NUnit.Framework; - using Unicast; - using Unicast.Messages; + using Testing; [TestFixture] - class When_applying_the_databus_message_mutator_to_null_properties : on_the_bus + class When_applying_the_databus_message_mutator_to_null_properties { [Test] - public void Should_not_blow_up() + public async Task Should_not_blow_up() { - var metadata = new MessageMetadata(timeToBeReceived: TimeSpan.FromDays(1)); - var message = new LogicalMessage(metadata, new MessageWithNullDataBusProperty(), new Dictionary(), null); - var context = new OutgoingContext(null,new SendOptions(Address.Parse("MyEndpoint")), message); + var context = new TestableOutgoingLogicalMessageContext(); + context.Message = new OutgoingLogicalMessage(typeof(MessageWithNullDataBusProperty), new MessageWithNullDataBusProperty()); + + var sendBehavior = new DataBusSendBehavior(null, new DefaultDataBusSerializer(), new Conventions()); - using (var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, "test"); stream.Position = 0; - sendBehavior.Invoke(context, () => { }); + await sendBehavior.Invoke(context, ctx => TaskEx.CompletedTask); } } diff --git a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_outgoing_messages.cs b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_outgoing_messages.cs index 6ec622bf7be..269659d9468 100644 --- a/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_outgoing_messages.cs +++ b/src/NServiceBus.Core.Tests/DataBus/When_applying_the_databus_message_mutator_to_outgoing_messages.cs @@ -1,53 +1,75 @@ namespace NServiceBus.Core.Tests.DataBus { using System; - using System.Collections.Generic; using System.IO; - using NServiceBus.Pipeline.Contexts; + using System.Threading.Tasks; + using DeliveryConstraints; + using NServiceBus.DataBus; + using NServiceBus.Performance.TimeToBeReceived; + using NServiceBus.Pipeline; using NUnit.Framework; - using Rhino.Mocks; - using Unicast; - using Unicast.Messages; + using Testing; [TestFixture] - class When_applying_the_databus_message_mutator_to_outgoing_messages : on_the_bus + class When_applying_the_databus_message_mutator_to_outgoing_messages { [Test] - public void Outgoing_databus_properties_should_be_dehydrated() + public async Task Outgoing_databus_properties_should_be_dehydrated() { - var metadata = new MessageMetadata(); - var message = new LogicalMessage(metadata, new MessageWithDataBusProperty + var context = new TestableOutgoingLogicalMessageContext(); + context.Message = new OutgoingLogicalMessage(typeof(MessageWithDataBusProperty), new MessageWithDataBusProperty { DataBusProperty = new DataBusProperty("test") - }, new Dictionary(), null); + }); - Invoke(message); + var fakeDatabus = new FakeDataBus(); - dataBus.AssertWasCalled( - x => x.Put(Arg.Is.Anything, Arg.Is.Equal(TimeSpan.MaxValue))); - } + var sendBehavior = new DataBusSendBehavior(fakeDatabus, new DefaultDataBusSerializer(), new Conventions()); - void Invoke(LogicalMessage message) - { - - var context = new OutgoingContext(null, new SendOptions(Address.Parse("MyEndpoint")), message); + await sendBehavior.Invoke(context, ctx => TaskEx.CompletedTask); - sendBehavior.Invoke(context, () => { }); + Assert.AreEqual(TimeSpan.MaxValue, fakeDatabus.TTBRUsed); } [Test] - public void Time_to_live_should_be_passed_on_the_databus() + public async Task Time_to_live_should_be_passed_on_the_databus() { - var metadata = new MessageMetadata(timeToBeReceived: TimeSpan.FromMinutes(1)); - var message = new LogicalMessage(metadata, new MessageWithExplicitTimeToLive + var context = new TestableOutgoingLogicalMessageContext(); + context.Message = new OutgoingLogicalMessage(typeof(MessageWithExplicitTimeToLive), new MessageWithExplicitTimeToLive { DataBusProperty = new DataBusProperty("test") - }, new Dictionary(), null); + }); + + context.Extensions.AddDeliveryConstraint(new DiscardIfNotReceivedBefore(TimeSpan.FromMinutes(1))); + + var fakeDatabus = new FakeDataBus(); + + var sendBehavior = new DataBusSendBehavior(fakeDatabus, new DefaultDataBusSerializer(), new Conventions()); + + await sendBehavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(TimeSpan.FromMinutes(1), fakeDatabus.TTBRUsed); + } + + class FakeDataBus : IDataBus + { + public Task Get(string key) + { + throw new NotImplementedException(); + } + + public Task Put(Stream stream, TimeSpan timeToBeReceived) + { + TTBRUsed = timeToBeReceived; + return Task.FromResult(Guid.NewGuid().ToString()); + } + + public Task Start() + { + throw new NotImplementedException(); + } - Invoke(message); - - dataBus.AssertWasCalled( - x => x.Put(Arg.Is.Anything, Arg.Is.Equal(TimeSpan.FromMinutes(1)))); + public TimeSpan TTBRUsed; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DataBus/When_nservicebus_is_initalizing.cs b/src/NServiceBus.Core.Tests/DataBus/When_nservicebus_is_initalizing.cs deleted file mode 100644 index e3b09b9eadc..00000000000 --- a/src/NServiceBus.Core.Tests/DataBus/When_nservicebus_is_initalizing.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace NServiceBus.Core.Tests.DataBus -{ - using System; - using System.Diagnostics; - using System.IO; - using NServiceBus.DataBus; - using NServiceBus.DataBus.InMemory; - using NServiceBus.Features; - using NUnit.Framework; - - [TestFixture] - public class When_nservicebus_is_initializing - { - [Test] - public void Databus_should_be_activated_if_a_databus_property_is_found() - { - var builder = new BusConfiguration(); - - builder.EndpointName("xyz"); - builder.TypesToScan(new[]{typeof(MessageWithDataBusProperty)}); - builder.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.SingleInstance)); - - var config = builder.BuildConfiguration(); - - Assert.True(new DataBusFileBased().CheckPrerequisites(new FeatureConfigurationContext(config)).IsSatisfied); - } - - [Test] - public void Databus_should_not_be_activated_if_no_databus_property_is_found() - { - var builder = new BusConfiguration(); - - builder.EndpointName("xyz"); - builder.TypesToScan(new[] { typeof(MessageWithoutDataBusProperty) }); - - var feature = new DataBus(); - - Assert.False(feature.CheckPrerequisites(new FeatureConfigurationContext(builder.BuildConfiguration())).IsSatisfied); - } - - [Test] - public void Should_throw_if_propertyType_is_not_serializable() - { - if (!Debugger.IsAttached) - { - Assert.Ignore("This only work in debug mode."); - } - - var builder = new BusConfiguration(); - builder.EndpointName("xyz"); - builder.TypesToScan(new[] - { - typeof(MessageWithNonSerializableDataBusProperty) - }); - builder.Conventions().DefiningDataBusPropertiesAs(p => p.Name.EndsWith("DataBus")); - - var feature = new DataBusFileBased(); - - Assert.Throws(() => feature.CheckPrerequisites(new FeatureConfigurationContext(builder.BuildConfiguration()))); - } - - [Test] - public void Should_not_throw_propertyType_is_not_serializable_if_a_IDataBusSerializer_is_already_registered() - { - if (!Debugger.IsAttached) - { - Assert.Ignore("This only work in debug mode."); - } - - var builder = new BusConfiguration(); - builder.EndpointName("xyz"); - builder.TypesToScan(new[] - { - typeof(MessageWithNonSerializableDataBusProperty) - }); - builder.Conventions().DefiningDataBusPropertiesAs(p => p.Name.EndsWith("DataBus")); - builder.RegisterComponents(c => - { - c.RegisterSingleton(new InMemoryDataBus()); - c.ConfigureComponent(() => new MyDataBusSerializer(), DependencyLifecycle.SingleInstance); - }); - - var config = builder.BuildConfiguration(); - var feature = new DataBusFileBased(); - - Assert.DoesNotThrow(() => feature.CheckPrerequisites(new FeatureConfigurationContext(config))); - } - - class MyDataBusSerializer : IDataBusSerializer - { - public void Serialize(object databusProperty, Stream stream) - { - throw new NotImplementedException(); - } - - public object Deserialize(Stream stream) - { - throw new NotImplementedException(); - } - } - } - - -} diff --git a/src/NServiceBus.Core.Tests/DataBus/on_the_bus.cs b/src/NServiceBus.Core.Tests/DataBus/on_the_bus.cs deleted file mode 100644 index 64da87a4b25..00000000000 --- a/src/NServiceBus.Core.Tests/DataBus/on_the_bus.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace NServiceBus.Core.Tests.DataBus -{ - using NServiceBus.DataBus; - using NUnit.Framework; - using Rhino.Mocks; - using Conventions = NServiceBus.Conventions; - - class on_the_bus - { - protected IDataBus dataBus; - protected DataBusSendBehavior sendBehavior; - protected DataBusReceiveBehavior receiveBehavior; - - [SetUp] - public void SetUp() - { - dataBus = MockRepository.GenerateMock(); - - receiveBehavior = new DataBusReceiveBehavior - { - DataBus = dataBus, - DataBusSerializer = new DefaultDataBusSerializer(), - Conventions = new Conventions(), - }; - - sendBehavior = new DataBusSendBehavior - { - DataBus = dataBus, - Conventions = new Conventions(), - DataBusSerializer = new DefaultDataBusSerializer(), - }; - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DateTimeExtensionsTests.cs b/src/NServiceBus.Core.Tests/DateTimeExtensionsTests.cs new file mode 100644 index 00000000000..6d9b7748b4b --- /dev/null +++ b/src/NServiceBus.Core.Tests/DateTimeExtensionsTests.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.Core.Tests +{ + using System; + using System.Globalization; + using NUnit.Framework; + + [TestFixture] + class DateTimeExtensionsTests + { + [Test] + public void When_roundtripping_constructed_date_should_be_equal() + { + var date = new DateTime(2016, 8, 29, 16, 37, 25, 75, DateTimeKind.Utc); + var dateString = DateTimeExtensions.ToWireFormattedString(date); + var result = DateTimeExtensions.ToUtcDateTime(dateString); + + Assert.AreEqual(date, result); + } + + [Test] + public void When_roundtripping_UtcNow_should_be_accurate_to_microseconds() + { + var date = DateTime.UtcNow; + var dateString = DateTimeExtensions.ToWireFormattedString(date); + var result = DateTimeExtensions.ToUtcDateTime(dateString); + + Assert.AreEqual(date.Year, result.Year); + Assert.AreEqual(date.Month, result.Month); + Assert.AreEqual(date.Day, result.Day); + Assert.AreEqual(date.Hour, result.Hour); + Assert.AreEqual(date.Minute, result.Minute); + Assert.AreEqual(date.Second, result.Second); + Assert.AreEqual(date.Millisecond, result.Millisecond); + Assert.AreEqual(date.Microseconds(), result.Microseconds()); + } + + [Test] + public void When_converting_string_should_be_accurate_to_microseconds() + { + var dateString = "2016-08-16 10:06:20:123456 Z"; + var date = DateTime.ParseExact(dateString, "yyyy-MM-dd HH:mm:ss:ffffff Z", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + var result = DateTimeExtensions.ToUtcDateTime(dateString); + + Assert.AreEqual(date.Year, result.Year); + Assert.AreEqual(date.Month, result.Month); + Assert.AreEqual(date.Day, result.Day); + Assert.AreEqual(date.Hour, result.Hour); + Assert.AreEqual(date.Minute, result.Minute); + Assert.AreEqual(date.Second, result.Second); + Assert.AreEqual(date.Millisecond, result.Millisecond); + Assert.AreEqual(date.Microseconds(), result.Microseconds()); + } + + [Test] + public void When_converting_string_that_is_too_short_should_throw() + { + var dateString = "201-08-16 10:06:20:123456 Z"; + + var exception = Assert.Throws(() => DateTimeExtensions.ToUtcDateTime(dateString)); + Assert.AreEqual(exception.Message, "String was not recognized as a valid DateTime."); + } + + [Test] + public void When_converting_string_with_invalid_characters_should_throw() + { + var dateString = "201j-08-16 10:06:20:123456 Z"; + + var exception = Assert.Throws(() => DateTimeExtensions.ToUtcDateTime(dateString)); + Assert.AreEqual(exception.Message, "String was not recognized as a valid DateTime."); + } + } +} diff --git a/src/NServiceBus.Core.Tests/DelayedDelivery/DelayedDeliveryOptionExtensionsTests.cs b/src/NServiceBus.Core.Tests/DelayedDelivery/DelayedDeliveryOptionExtensionsTests.cs new file mode 100644 index 00000000000..d85e8934b34 --- /dev/null +++ b/src/NServiceBus.Core.Tests/DelayedDelivery/DelayedDeliveryOptionExtensionsTests.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.Core.Tests.Timeout +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class DelayedDeliveryOptionExtensionsTests + { + [Test] + public void GetDeliveryDelay_Should_Return_The_Configured_Delay_TimeSpan() + { + var options = new SendOptions(); + var delay = TimeSpan.FromMinutes(42); + options.DelayDeliveryWith(delay); + + Assert.AreEqual(delay, options.GetDeliveryDelay()); + } + + [Test] + public void GetDeliveryDelay_Should_Return_Null_When_No_Delay_Configured() + { + var options = new SendOptions(); + + Assert.IsNull(options.GetDeliveryDelay()); + } + + [Test] + public void GetDeliveryDate_Should_Return_The_Configured_Delivery_Date() + { + var options = new SendOptions(); + DateTimeOffset deliveryDate = new DateTime(2012, 12, 12, 12, 12, 12); + options.DoNotDeliverBefore(deliveryDate); + + Assert.AreEqual(deliveryDate, options.GetDeliveryDate()); + } + + [Test] + public void GetDeliveryDate_Should_Return_Null_When_No_Date_Configured() + { + var options = new SendOptions(); + + Assert.IsNull(options.GetDeliveryDate()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehaviorTests.cs b/src/NServiceBus.Core.Tests/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehaviorTests.cs new file mode 100644 index 00000000000..6a87e4506e6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehaviorTests.cs @@ -0,0 +1,112 @@ +namespace NServiceBus.Core.Tests.Timeout +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using NServiceBus.Performance.TimeToBeReceived; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + + class RouteDeferredMessageToTimeoutManagerBehaviorTests + { + [Test] + public async Task Should_reroute_to_the_timeout_manager() + { + var behavior = new RouteDeferredMessageToTimeoutManagerBehavior("tm"); + var delay = TimeSpan.FromDays(1); + + var headers = new Dictionary(); + string destination = null; + var context = CreateContext(new UnicastRoutingStrategy("target"), new DelayDeliveryWith(delay)); + + await behavior.Invoke(context, c => + { + var addressTag = (UnicastAddressTag) c.RoutingStrategies.First().Apply(headers); + destination = addressTag.Destination; + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("tm", destination); + Assert.AreEqual(headers[TimeoutManagerHeaders.RouteExpiredTimeoutTo], "target"); + } + + [Test] + public void Supports_only_unicast_routing() + { + var behavior = new RouteDeferredMessageToTimeoutManagerBehavior("tm"); + var delay = TimeSpan.FromDays(1); + + var context = CreateContext(new MulticastRoutingStrategy(null), new DelayDeliveryWith(delay)); + + Assert.That(async () => await behavior.Invoke(context, ctx => TaskEx.CompletedTask), Throws.InstanceOf().And.Message.Contains("Delayed delivery using the Timeout Manager is only supported for messages with unicast routing")); + } + + [Test] + public void Cannot_be_combined_with_time_to_be_received() + { + var behavior = new RouteDeferredMessageToTimeoutManagerBehavior("tm"); + var delay = TimeSpan.FromDays(1); + + var context = CreateContext(new UnicastRoutingStrategy("target"), new DelayDeliveryWith(delay), new DiscardIfNotReceivedBefore(TimeSpan.FromSeconds(30))); + + Assert.That(async () => await behavior.Invoke(context, ctx => TaskEx.CompletedTask), Throws.InstanceOf().And.Message.Contains("Postponed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to postpone messages of this type.")); + } + + [Test] + public async Task Should_set_the_expiry_header_to_a_absolute_utc_time_calculated_based_on_delay() + { + var behavior = new RouteDeferredMessageToTimeoutManagerBehavior("tm"); + var delay = TimeSpan.FromDays(1); + + var headers = new Dictionary(); + var context = CreateContext(new UnicastRoutingStrategy("target"), new DelayDeliveryWith(delay)); + + await behavior.Invoke(context, c => + { + c.RoutingStrategies.First().Apply(headers); + return TaskEx.CompletedTask; + }); + + Assert.LessOrEqual(DateTimeExtensions.ToUtcDateTime(headers[TimeoutManagerHeaders.Expire]), DateTime.UtcNow + delay); + } + + [Test] + public async Task Should_set_the_expiry_header_to_a_absolute_utc_time() + { + var behavior = new RouteDeferredMessageToTimeoutManagerBehavior("tm"); + var at = DateTime.UtcNow + TimeSpan.FromDays(1); + + var headers = new Dictionary(); + var context = CreateContext(new UnicastRoutingStrategy("target"), new DoNotDeliverBefore(at)); + + await behavior.Invoke(context, c => + { + c.RoutingStrategies.First().Apply(headers); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual(headers[TimeoutManagerHeaders.Expire], DateTimeExtensions.ToWireFormattedString(at)); + } + + TestableRoutingContext CreateContext(RoutingStrategy routingStrategy, params DeliveryConstraint[] deliveryConstraints) + { + var context = new TestableRoutingContext + { + RoutingStrategies = new List + { + routingStrategy + } + }; + foreach (var deliveryContraint in deliveryConstraints) + { + context.Extensions.AddDeliveryConstraint(deliveryContraint); + } + + return context; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/DispatchTimeoutBehaviorTest.cs b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/DispatchTimeoutBehaviorTest.cs new file mode 100644 index 00000000000..a628f99c443 --- /dev/null +++ b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/DispatchTimeoutBehaviorTest.cs @@ -0,0 +1,186 @@ +namespace NServiceBus.Core.Tests.Timeout.TimeoutManager +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Timeout.Core; + using NUnit.Framework; + using Transport; + + public class DispatchTimeoutBehaviorTest + { + [Test] + public async Task Invoke_when_message_dispatched_should_remove_timeout_from_timeout_storage() + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + var timeoutData = CreateTimeout(); + await timeoutPersister.Add(timeoutData, null); + + await behavior.Invoke(CreateContext(timeoutData.Id)); + + var result = await timeoutPersister.Peek(timeoutData.Id, null); + Assert.Null(result); + } + + [Test] + public async Task Invoke_when_timeout_not_in_storage_should_process_successfully() + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + + await behavior.Invoke(CreateContext(Guid.NewGuid().ToString())); + + Assert.AreEqual(0, messageDispatcher.OutgoingTransportOperations.UnicastTransportOperations.Count()); + } + + [Test] + public async Task Invoke_when_dispatching_message_fails_should_keep_timeout_in_storage() + { + var messageDispatcher = new FailingMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + var timeoutData = CreateTimeout(); + await timeoutPersister.Add(timeoutData, null); + + Assert.That(async () => await behavior.Invoke(CreateContext(timeoutData.Id)), Throws.InstanceOf()); + + var result = await timeoutPersister.Peek(timeoutData.Id, null); + Assert.NotNull(result); + } + + [Test] + public void Invoke_when_removing_timeout_fails_should_throw_exception() + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new FakeTimeoutStorage + { + OnPeek = (id, bag) => CreateTimeout(), + OnTryRemove = (id, bag) => false // simulates a concurrent delete + }; + + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + + Assert.That(async () => await behavior.Invoke(CreateContext(Guid.NewGuid().ToString())), Throws.InstanceOf()); + } + + [Test] + public async Task Invoke_when_using_dtc_should_enlist_dispatch_in_transaction() + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + var timeoutData = CreateTimeout(); + await timeoutPersister.Add(timeoutData, null); + + await behavior.Invoke(CreateContext(timeoutData.Id)); + + var transportOperation = messageDispatcher.OutgoingTransportOperations.UnicastTransportOperations.Single(); + Assert.AreEqual(DispatchConsistency.Default, transportOperation.RequiredDispatchConsistency); + } + + [Test] + public async Task Invoke_should_pass_transport_transaction_from_message_context() + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var timeoutData = CreateTimeout(); + await timeoutPersister.Add(timeoutData, null); + var context = CreateContext(timeoutData.Id); + + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, TransportTransactionMode.TransactionScope); + await behavior.Invoke(context); + + Assert.AreSame(context.TransportTransaction, messageDispatcher.TransportTransactionUsed, "Wrong transport transaction passed to the dispatcher"); + } + + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.None)] + public async Task Invoke_when_not_using_dtc_transport_should_not_enlist_dispatch_in_transaction(TransportTransactionMode nonDtcTxSettings) + { + var messageDispatcher = new FakeMessageDispatcher(); + var timeoutPersister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var behavior = new DispatchTimeoutBehavior(messageDispatcher, timeoutPersister, nonDtcTxSettings); + var timeoutData = CreateTimeout(); + await timeoutPersister.Add(timeoutData, null); + + await behavior.Invoke(CreateContext(timeoutData.Id)); + + var transportOperation = messageDispatcher.OutgoingTransportOperations.UnicastTransportOperations.Single(); + Assert.AreEqual(DispatchConsistency.Isolated, transportOperation.RequiredDispatchConsistency); + } + + static TimeoutData CreateTimeout() + { + return new TimeoutData + { + Destination = "endpointQueue", + Headers = new Dictionary() + }; + } + + static MessageContext CreateContext(string timeoutId) + { + var messageId = Guid.NewGuid().ToString("D"); + var headers = new Dictionary + { + {"Timeout.Id", timeoutId} + }; + + return new MessageContext(messageId, headers, new byte[0], new TransportTransaction(), new CancellationTokenSource(), new ContextBag()); + } + + class FakeMessageDispatcher : IDispatchMessages + { + public TransportOperations OutgoingTransportOperations { get; private set; } = new TransportOperations(); + public TransportTransaction TransportTransactionUsed { get; private set; } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transportTransaction, ContextBag context) + { + OutgoingTransportOperations = outgoingMessages; + TransportTransactionUsed = transportTransaction; + return TaskEx.CompletedTask; + } + } + + class FailingMessageDispatcher : IDispatchMessages + { + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transportTransaction, ContextBag context) + { + throw new Exception("simulated exception"); + } + } + + class FakeTimeoutStorage : IPersistTimeouts + { + public Func OnTryRemove { get; set; } = (id, bag) => true; + public Func OnPeek { get; set; } = (id, bag) => null; + + public Task Add(TimeoutData timeout, ContextBag context) + { + return TaskEx.CompletedTask; + } + + public Task TryRemove(string timeoutId, ContextBag context) + { + return Task.FromResult(OnTryRemove(timeoutId, context)); + } + + public Task Peek(string timeoutId, ContextBag context) + { + return Task.FromResult(OnPeek(timeoutId, context)); + } + + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) + { + return TaskEx.CompletedTask; + } + } + } +} diff --git a/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPollerTests.cs b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPollerTests.cs new file mode 100644 index 00000000000..382050b3724 --- /dev/null +++ b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPollerTests.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.Core.Tests.Timeout.TimeoutManager +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using NServiceBus.Timeout.Core; + using NUnit.Framework; + + public class ExpiredTimeoutsPollerTests + { + [SetUp] + public void SetUp() + { + breaker = new FakeBreaker(); + dispatcher = new RecordingFakeDispatcher(); + timeouts = new InMemoryTimeoutPersister(() => currentTime); + poller = new ExpiredTimeoutsPoller(timeouts, dispatcher, "test", breaker, () => currentTime); + } + + [TearDown] + public void TearDown() + { + poller.Dispose(); + } + + [Test] + public async Task Sends_no_messages_when_no_timeouts_registered() + { + await poller.SpinOnce(CancellationToken.None); + CollectionAssert.IsEmpty(dispatcher.DispatchedMessages); + } + + [Test] + public async Task Returns_to_normal_poll_cycle_after_dispatching_a_pushed_timeout() + { + await poller.SpinOnce(CancellationToken.None); + var nextRetrieval = poller.NextRetrieval; + + RegisterNewTimeout(nextRetrieval - HalfOfDefaultInMemoryPersisterSleep); + + currentTime = poller.NextRetrieval; + + await poller.SpinOnce(CancellationToken.None); + + Assert.AreEqual(1, dispatcher.DispatchedMessages.Count); + Assert.AreEqual(currentTime + InMemoryTimeoutPersister.EmptyResultsNextTimeToRunQuerySpan, poller.NextRetrieval); + } + + [Test] + public async Task Returns_to_normal_poll_cycle_after_dispatching_a_non_pushed_timeout() + { + var nextRetrieval = poller.NextRetrieval; + var timeout1 = nextRetrieval.Subtract(HalfOfDefaultInMemoryPersisterSleep); + // ReSharper disable once PossibleLossOfFraction + var timeout2 = timeout1.Add(TimeSpan.FromMilliseconds(HalfOfDefaultInMemoryPersisterSleep.Milliseconds/2)); + + RegisterNewTimeout(timeout1); + RegisterNewTimeout(timeout2, false); + + currentTime = timeout2; + await poller.SpinOnce(CancellationToken.None); + + Assert.AreEqual(2, dispatcher.DispatchedMessages.Count); + Assert.AreEqual(currentTime + InMemoryTimeoutPersister.EmptyResultsNextTimeToRunQuerySpan, poller.NextRetrieval); + } + + void RegisterNewTimeout(DateTime newTimeout, bool withNotification = true) + { + timeouts.Add(new TimeoutData + { + Time = newTimeout + }, null); + if (withNotification) + { + poller.NewTimeoutRegistered(newTimeout); + } + } + + FakeBreaker breaker; + RecordingFakeDispatcher dispatcher; + DateTime currentTime = DateTime.UtcNow; + // ReSharper disable once PossibleLossOfFraction + TimeSpan HalfOfDefaultInMemoryPersisterSleep = TimeSpan.FromMilliseconds(InMemoryTimeoutPersister.EmptyResultsNextTimeToRunQuerySpan.TotalMilliseconds/2); + ExpiredTimeoutsPoller poller; + InMemoryTimeoutPersister timeouts; + + class FakeBreaker : ICircuitBreaker + { + public void Success() + { + } + + public Task Failure(Exception exception) + { + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/RecordingFakeDispatcher.cs b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/RecordingFakeDispatcher.cs new file mode 100644 index 00000000000..3a7f571239f --- /dev/null +++ b/src/NServiceBus.Core.Tests/DelayedDelivery/TimeoutManager/RecordingFakeDispatcher.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Core.Tests.Timeout.TimeoutManager +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Transport; + + public class RecordingFakeDispatcher : IDispatchMessages + { + public class DispatchedMessage + { + public TransportOperations Operations { get; } + public ContextBag Context { get; } + public TransportTransaction Transaction { get; } + + public DispatchedMessage(TransportOperations operations, TransportTransaction transaction, ContextBag context) + { + Operations = operations; + Context = context; + Transaction = transaction; + } + } + + public readonly List DispatchedMessages = new List(); + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + DispatchedMessages.Add(new DispatchedMessage(outgoingMessages, transaction, context)); + return TaskEx.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DeliveryConstraintContextExtensions/DeliveryConstraintContextExtensionsTests.cs b/src/NServiceBus.Core.Tests/DeliveryConstraintContextExtensions/DeliveryConstraintContextExtensionsTests.cs new file mode 100644 index 00000000000..2805707b30f --- /dev/null +++ b/src/NServiceBus.Core.Tests/DeliveryConstraintContextExtensions/DeliveryConstraintContextExtensionsTests.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.Core.Tests.DeliveryConstraintContextExtensions +{ + using System; + using System.Collections.Generic; + using Extensibility; + using DelayedDelivery; + using DeliveryConstraints; + using NServiceBus.Features; + using NServiceBus.Routing; + using Settings; + using Transport; + using NUnit.Framework; + + [TestFixture] + public class DeliveryConstraintContextExtensionsTests + { + [Test] + public void Should_be_able_to_determine_if_delivery_constraint_is_supported() + { + var settings = new SettingsHolder(); + var fakeTransportDefinition = new FakeTransportDefinition(); + settings.Set(fakeTransportDefinition); + settings.Set(fakeTransportDefinition.Initialize(settings, null)); + + var context = new FeatureConfigurationContext(settings, null, null); + var result = context.Settings.DoesTransportSupportConstraint(); + Assert.IsTrue(result); + } + + [Test] + public void Should_be_able_to_try_remove_constraints() + { + var context = new ContextBag(); + + DelayDeliveryWith with; + var resultBeforeAdd = context.TryRemoveDeliveryConstraint(out with); + + context.AddDeliveryConstraint(new DelayDeliveryWith(TimeSpan.FromHours(1))); + var resultAfterAdd = context.TryRemoveDeliveryConstraint(out with); + + Assert.IsFalse(resultBeforeAdd); + Assert.IsTrue(resultAfterAdd); + } + + class FakeTransportDefinition : TransportDefinition + { + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + return new FakeTransportInfrastructure(); + } + + public override string ExampleConnectionStringForErrorMessage { get; } = string.Empty; + } + + class FakeTransportInfrastructure : TransportInfrastructure + { + + public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) + { + throw new NotImplementedException(); + } + + public override string ToTransportAddress(LogicalAddress logicalAddress) + { + throw new NotImplementedException(); + } + + public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() + { + throw new NotImplementedException(); + } + + public override TransportSendInfrastructure ConfigureSendInfrastructure() + { + throw new NotImplementedException(); + } + + public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() + { + throw new NotImplementedException(); + } + + public override IEnumerable DeliveryConstraints { get; } = new[] { typeof(DelayDeliveryWith) }; + + public override TransportTransactionMode TransactionMode { get; } = TransportTransactionMode.None; + + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/DocumentationTests.cs b/src/NServiceBus.Core.Tests/DocumentationTests.cs new file mode 100644 index 00000000000..f3ac6c55245 --- /dev/null +++ b/src/NServiceBus.Core.Tests/DocumentationTests.cs @@ -0,0 +1,282 @@ +namespace NServiceBus.Core.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Xml.Linq; + using NuDoq; + using NUnit.Framework; + using Enum = NuDoq.Enum; + using Exception = System.Exception; + using Text = NuDoq.Text; + + [TestFixture] + public class DocumentationTests + { + [Test] + public void EnsureNoDocumentationIsEmpty() + { + var assembly = typeof(Endpoint).Assembly; + var codeBase = assembly.CodeBase; + var uri = new UriBuilder(codeBase); + var path = Uri.UnescapeDataString(uri.Path); + var assemblyMembers = DocReader.Read(assembly, Path.ChangeExtension(path, "xml")); + + var list = GetListOfMissingDoco(assemblyMembers).ToList(); + + if (list.Any()) + { + var errors = string.Join("\r\n", list); + throw new Exception("Some members have empty documentation or have a sentence that does not end with a period:\r\n" + errors); + } + } + + static IEnumerable GetListOfMissingDoco(AssemblyMembers assemblyMembers) + { + var visitor = new VerificationVisitor(); + visitor.VisitAssembly(assemblyMembers); + return visitor.BadMembers + .Distinct() + .Select(FullMemberName); + } + + static string FullMemberName(MemberInfo member) + { + if (member.ReflectedType != null) + { + var method = member as MethodBase; + if (method != null) + { + var methodInfo = method; + var parameters = string.Join(", ", methodInfo.GetParameters().Select(x => x.ParameterType.Name + " " + x.Name)); + return $"{method.ReflectedType.FullName}.{method.Name}({parameters})"; + } + return $"{member.ReflectedType.FullName}.{member.Name}"; + } + return member.Name; + } + + public class VerificationVisitor : Visitor + { + Stack memberInfos = new Stack(); + + public List BadMembers = new List(); + + protected override void VisitElement(Element element) + { + AddIfEmpty(element); + base.VisitElement(element); + } + + void AddIfEmpty(Element element) + { + if (element is TypeParamRef) + { + return; + } + if (element is C) + { + return; + } + if (element is SeeAlso) + { + return; + } + if (element is UnknownElement) + { + return; + } + if (element is Code) + { + return; + } + if (element is See) + { + return; + } + if (element is Text) + { + return; + } + if (element is ParamRef) + { + return; + } + var text = element.ToText(); + if (text == null) + { + return; + } + if (!string.IsNullOrWhiteSpace(text)) + { + if (text.Trim().EndsWith(".")) + { + return; + } + } + if (memberInfos.Count==0) + { + return; + } + var currentMember = memberInfos.Peek(); + if (currentMember == null) + { + return; + } + if (IsInheritDoc(element)) + { + return; + } + if (currentMember.DeclaringType != null && currentMember.DeclaringType.FullName.Contains("JetBrains")) + { + return; + } + BadMembers.Add(currentMember); + } + + bool IsInheritDoc(Element element) + { + var lineInfoField = GetLineInfoField(element.GetType()); + var lineInfo = (XElement) lineInfoField?.GetValue(element); + + if (lineInfo != null) + { + var firstNode = lineInfo.FirstNode; + if (firstNode.ToString().Contains("inheritdoc")) + { + return true; + } + } + return false; + } + + void ClearMember() + { + memberInfos.Pop(); + } + void SetMember(MemberInfo memberInfo) + { + if (memberInfo == null) + { + memberInfos.Push(null); + return; + } + + var method = memberInfo as MethodBase; + if (method != null) + { + if (!method.DeclaringType.IsVisible) + { + memberInfos.Push(null); + return; + } + } + var constructorInfo = memberInfo as ConstructorInfo; + if (constructorInfo != null) + { + if (!constructorInfo.DeclaringType.IsVisible) + { + memberInfos.Push(null); + return; + } + } + var type = memberInfo as Type; + if (type != null) + { + if (!type.IsVisible) + { + memberInfos.Push(null); + return; + } + } + memberInfos.Push(memberInfo); + } + + Dictionary fieldCache = new Dictionary(); + + FieldInfo GetLineInfoField(Type type) + { + FieldInfo cachedField; + var handle = type.TypeHandle; + if (fieldCache.TryGetValue(handle, out cachedField)) + { + return cachedField; + } + var currentType = type; + while (currentType != typeof(object)) + { + foreach (var field in currentType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)) + { + if (field.Name == "lineInfo") + { + return fieldCache[handle] = field; + } + } + currentType = currentType.BaseType; + } + return fieldCache[handle] = null; + } + + public override void VisitClass(Class type) + { + SetMember(type.Info); + base.VisitClass(type); + ClearMember(); + } + + + public override void VisitEnum(Enum type) + { + SetMember(type.Info); + base.VisitEnum(type); + ClearMember(); + } + + public override void VisitEvent(Event @event) + { + SetMember(@event.Info); + base.VisitEvent(@event); + ClearMember(); + } + + public override void VisitStruct(Struct type) + { + SetMember(type.Info); + base.VisitStruct(type); + ClearMember(); + } + + + public override void VisitInterface(Interface type) + { + SetMember(type.Info); + base.VisitInterface(type); + ClearMember(); + } + + public override void VisitField(Field field) + { + SetMember(field.Info); + base.VisitField(field); + ClearMember(); + } + + public override void VisitMethod(Method method) + { + SetMember(method.Info); + base.VisitMethod(method); + ClearMember(); + } + + public override void VisitProperty(Property property) + { + SetMember(property.Info); + base.VisitProperty(property); + ClearMember(); + } + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/ConfigureRijndaelEncryptionServiceTests.cs b/src/NServiceBus.Core.Tests/Encryption/ConfigureRijndaelEncryptionServiceTests.cs index 5dea0646770..b3c43d37c45 100644 --- a/src/NServiceBus.Core.Tests/Encryption/ConfigureRijndaelEncryptionServiceTests.cs +++ b/src/NServiceBus.Core.Tests/Encryption/ConfigureRijndaelEncryptionServiceTests.cs @@ -86,7 +86,8 @@ public void Should_throw_for_overlapping_keys() "key1" }; var exception = Assert.Throws(() => ConfigureRijndaelEncryptionService.VerifyKeys(keys)); - Assert.AreEqual("Overlapping keys defined. Please ensure that no keys overlap.\r\nParameter name: expiredKeys", exception.Message); + StringAssert.StartsWith("Overlapping keys defined. Ensure that no keys overlap.", exception.Message); + Assert.AreEqual("expiredKeys", exception.ParamName); } [Test] @@ -99,7 +100,8 @@ public void Should_throw_for_whitespace_key() "key2" }; var exception = Assert.Throws(() => ConfigureRijndaelEncryptionService.VerifyKeys(keys)); - Assert.AreEqual("Empty encryption key detected in position 1.\r\nParameter name: expiredKeys", exception.Message); + StringAssert.StartsWith("Empty encryption key detected in position 1.", exception.Message); + Assert.AreEqual("expiredKeys", exception.ParamName); } [Test] @@ -112,7 +114,8 @@ public void Should_throw_for_null_key() "key2" }; var exception = Assert.Throws(() => ConfigureRijndaelEncryptionService.VerifyKeys(keys)); - Assert.AreEqual("Empty encryption key detected in position 1.\r\nParameter name: expiredKeys", exception.Message); + StringAssert.StartsWith("Empty encryption key detected in position 1.", exception.Message); + Assert.AreEqual("expiredKeys", exception.ParamName); } [Test] diff --git a/src/NServiceBus.Core.Tests/Encryption/ConventionBasedEncryptedStringSpecs.cs b/src/NServiceBus.Core.Tests/Encryption/ConventionBasedEncryptedStringSpecs.cs index def0eea9cf0..2475476ef3e 100644 --- a/src/NServiceBus.Core.Tests/Encryption/ConventionBasedEncryptedStringSpecs.cs +++ b/src/NServiceBus.Core.Tests/Encryption/ConventionBasedEncryptedStringSpecs.cs @@ -1,60 +1,35 @@ namespace NServiceBus.Core.Tests.Encryption { using System; + using System.Linq; using NUnit.Framework; - using Conventions = NServiceBus.Conventions; [TestFixture] - public class When_sending_a_message_with_user_defined_convention : UserDefinedConventionContext + public class When_inspecting_a_message_with_user_defined_convention : UserDefinedConventionContext { [Test] - public void Should_encrypt_the_value() + public void Should_return_the_value() { var message = new ConventionBasedSecureMessage { EncryptedSecret = "A secret" }; - mutator.MutateOutgoing(message, null); - Assert.AreEqual(string.Format("{0}@{1}", "encrypted value", "init_vector"), message.EncryptedSecret); - } - } - - [TestFixture] - public class When_receiving_a_message_with_user_defined_convention : UserDefinedConventionContext - { - [Test] - public void Should_encrypt_the_value() - { - var message = new ConventionBasedSecureMessage - { - EncryptedSecret = "encrypted value@init_vector" - }; - mutator.MutateIncoming(message, null); + var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual("A secret", message.EncryptedSecret); - } - } - - [TestFixture] - public class When_encrypting_a_property_that_is_not_a_string : UserDefinedConventionContext - { - [Test] - public void Should_throw_an_exception() - { - var exception = Assert.Throws(() => mutator.MutateOutgoing(new MessageWithNonStringSecureProperty(), null)); - Assert.AreEqual("Only string properties is supported for convention based encryption, please check your convention", exception.Message); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("EncryptedSecret", result[0].Item2.Name); } } [TestFixture] - public class When_decrypting_a_property_that_is_not_a_string : UserDefinedConventionContext + public class When_inspecting_a_property_that_is_not_a_string : UserDefinedConventionContext { [Test] public void Should_throw_an_exception() { - var exception = Assert.Throws(() => mutator.MutateIncoming(new MessageWithNonStringSecureProperty(), null)); - Assert.AreEqual("Only string properties is supported for convention based encryption, please check your convention", exception.Message); + var exception = Assert.Throws(() => inspector.ScanObject(new MessageWithNonStringSecureProperty()).ToList()); + Assert.AreEqual("Only string properties are supported for convention based encryption. Check the configured conventions.", exception.Message); } } diff --git a/src/NServiceBus.Core.Tests/Encryption/FakeEncryptionService.cs b/src/NServiceBus.Core.Tests/Encryption/FakeEncryptionService.cs index a268bc52041..84c08638b46 100644 --- a/src/NServiceBus.Core.Tests/Encryption/FakeEncryptionService.cs +++ b/src/NServiceBus.Core.Tests/Encryption/FakeEncryptionService.cs @@ -1,23 +1,23 @@ namespace NServiceBus.Core.Tests.Encryption { using System; - using NServiceBus.Encryption; + using NServiceBus.Pipeline; public class FakeEncryptionService : IEncryptionService { - readonly EncryptedValue hardcodedValue; + EncryptedValue hardcodedValue; public FakeEncryptionService(EncryptedValue hardcodedValue) { this.hardcodedValue = hardcodedValue; } - public EncryptedValue Encrypt(string value) + public EncryptedValue Encrypt(string value, IOutgoingLogicalMessageContext context) { return hardcodedValue; } - public string Decrypt(EncryptedValue encryptedValue) + public string Decrypt(EncryptedValue encryptedValue, IIncomingLogicalMessageContext context) { if (encryptedValue.Base64Iv == hardcodedValue.Base64Iv && encryptedValue.EncryptedBase64Value == hardcodedValue.EncryptedBase64Value) return "A secret"; diff --git a/src/NServiceBus.Core.Tests/Encryption/Issue_701.cs b/src/NServiceBus.Core.Tests/Encryption/Issue_701.cs index cf7a916015e..3a1c61b4d1e 100644 --- a/src/NServiceBus.Core.Tests/Encryption/Issue_701.cs +++ b/src/NServiceBus.Core.Tests/Encryption/Issue_701.cs @@ -1,5 +1,6 @@ namespace NServiceBus.Core.Tests.Encryption { + using System.Linq; using NUnit.Framework; [TestFixture] @@ -13,9 +14,9 @@ public void No_get_on_property() Name = "John" }; - var result = (TestMessageWithSets)mutator.MutateOutgoing(message, null); + var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual("John", result.Name); + Assert.AreEqual(0, result.Count); } [Test] @@ -26,12 +27,12 @@ public void No_set_on_property() Name = "John" }; - var result = (TestMessageWithGets)mutator.MutateOutgoing(message, null); + var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual("John", result.Name); + Assert.AreEqual(0, result.Count); } - private class TestMessageWithSets + class TestMessageWithSets { public string Name { get; set; } @@ -54,19 +55,13 @@ public int Options2 } } - private class TestMessageWithGets + class TestMessageWithGets { public string Name { get; set; } - public string Options1 - { - get { return "Testing"; } - } + public string Options1 => "Testing"; - public int Options2 - { - get { return 5; } - } + public int Options2 => 5; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/Issue_949.cs b/src/NServiceBus.Core.Tests/Encryption/Issue_949.cs index 9cc48b594aa..323c5507b21 100644 --- a/src/NServiceBus.Core.Tests/Encryption/Issue_949.cs +++ b/src/NServiceBus.Core.Tests/Encryption/Issue_949.cs @@ -11,12 +11,12 @@ public void null_element_in_primitive_array() { var message = new TestMessageWithPrimitives { - Data = new int?[] {null, 1} + Data = new int?[] { null, 1 } }; - mutator.MutateOutgoing(message, null); + inspector.ScanObject(message); - Assert.AreEqual(new int?[] { null, 1}, message.Data); + Assert.AreEqual(new int?[] { null, 1 }, message.Data); } [Test] @@ -24,21 +24,21 @@ public void null_element_in_object_array() { var message = new TestMessageWithObjects { - Data = new object[] {null, this, null} + Data = new object[] { null, this, null } }; - mutator.MutateOutgoing(message, null); + inspector.ScanObject(message); + + Assert.AreEqual(new object[] { null, this, null }, message.Data); - Assert.AreEqual(new object[] { null, this,null }, message.Data); - } - private class TestMessageWithPrimitives + class TestMessageWithPrimitives { public int?[] Data; } - private class TestMessageWithObjects + class TestMessageWithObjects { public object[] Data; } diff --git a/src/NServiceBus.Core.Tests/Encryption/Mailing_list_complex_dto.cs b/src/NServiceBus.Core.Tests/Encryption/Mailing_list_complex_dto.cs index 9dd255c455e..7ae55399967 100644 --- a/src/NServiceBus.Core.Tests/Encryption/Mailing_list_complex_dto.cs +++ b/src/NServiceBus.Core.Tests/Encryption/Mailing_list_complex_dto.cs @@ -11,52 +11,40 @@ public void Indexed_enum_property() { var message = new TestDto(); - var dict = message.Options[TestEnum.EnumValue1]; - dict["test"] = "aString"; - message.Options[TestEnum.EnumValue1]["test"] = "aString"; - var result = (TestDto)mutator.MutateOutgoing(message, null); - - Assert.True(result.Options.ContainsKey(TestEnum.EnumValue1)); + inspector.ScanObject(message); } - private enum TestEnum + enum TestEnum { EnumValue1 } - private class TestOptions + class TestOptions { - private readonly Dictionary> _dictionary = new Dictionary>(); - public Dictionary> Dictionary { get { return _dictionary; } } + public Dictionary> Dictionary { get; } = new Dictionary>(); public bool ContainsKey(TestEnum key) { - return _dictionary.ContainsKey(key); + return Dictionary.ContainsKey(key); } - public IEnumerable Keys { get { return _dictionary.Keys; } } + public IEnumerable Keys => Dictionary.Keys; - public Dictionary this[TestEnum appEnum] - { - get - { - return _dictionary.ContainsKey(appEnum) - ? _dictionary[appEnum] - : _dictionary[appEnum] = new Dictionary(); - } - } + public Dictionary this[TestEnum appEnum] => Dictionary.ContainsKey(appEnum) + ? Dictionary[appEnum] + : Dictionary[appEnum] = new Dictionary(); } - private class TestDto + class TestDto { public TestDto() { Options = new TestOptions(); } - public TestOptions Options { get; set; } + public TestOptions Options { get; } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/RijndaelEncryptionServiceTest.cs b/src/NServiceBus.Core.Tests/Encryption/RijndaelEncryptionServiceTest.cs index 83a490e7e75..d1efa9ac819 100644 --- a/src/NServiceBus.Core.Tests/Encryption/RijndaelEncryptionServiceTest.cs +++ b/src/NServiceBus.Core.Tests/Encryption/RijndaelEncryptionServiceTest.cs @@ -4,8 +4,7 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Text; - using NServiceBus.Encryption.Rijndael; - using NServiceBus.Pipeline.Contexts; + using NServiceBus.Pipeline; using NUnit.Framework; [TestFixture] @@ -15,7 +14,10 @@ public class RijndaelEncryptionServiceTest public void Should_encrypt_and_decrypt() { var encryptionKey = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service = new RijndaelEncryptionServiceMock("encryptionKey", encryptionKey, new[] { encryptionKey }); + var service = new TestableRijndaelEncryptionService("encryptionKey", encryptionKey, new[] + { + encryptionKey + }); var encryptedValue = service.Encrypt("string to encrypt", null); Assert.AreNotEqual("string to encrypt", encryptedValue.EncryptedBase64Value); var decryptedValue = service.Decrypt(encryptedValue, null); @@ -26,18 +28,23 @@ public void Should_encrypt_and_decrypt() public void Should_encrypt_and_decrypt_for_expired_key() { var encryptionKey1 = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service1 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey1, new[] { encryptionKey1 }); + var encryptionIV = Encoding.ASCII.GetBytes("GaoKtfQo87igiaks"); + var service1 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey1, new[] + { + encryptionKey1 + }) + { + EncryptionIV = encryptionIV + }; var encryptedValue = service1.Encrypt("string to encrypt", null); Assert.AreNotEqual("string to encrypt", encryptedValue.EncryptedBase64Value); - var encryptionKey2 = Encoding.ASCII.GetBytes("vznkynwuvateefgduvsqjsufqfrrfcya"); - var expiredKeys = new List + var service2 = new TestableRijndaelEncryptionService("encryptionKey2", encryptionKey2, new List { encryptionKey2, encryptionKey1 - }; - var service2 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey2, expiredKeys); + }); var decryptedValue = service2.Decrypt(encryptedValue, null); Assert.AreEqual("string to encrypt", decryptedValue); @@ -47,7 +54,7 @@ public void Should_encrypt_and_decrypt_for_expired_key() public void Should_throw_when_decrypt_with_wrong_key() { var usedKey = Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - var service1 = new RijndaelEncryptionServiceMock("should-be-ignored-in-next-arrange", usedKey, new List()); + var service1 = new TestableRijndaelEncryptionService("should-be-ignored-in-next-arrange", usedKey, new List()); var encryptedValue = service1.Encrypt("string to encrypt", null); Assert.AreNotEqual("string to encrypt", encryptedValue.EncryptedBase64Value); @@ -57,7 +64,7 @@ public void Should_throw_when_decrypt_with_wrong_key() Encoding.ASCII.GetBytes("cccccccccccccccccccccccccccccccc") }; - var service2 = new RijndaelEncryptionServiceMock("should-be-ignored", usedKey, unusedExpiredKeys); + var service2 = new TestableRijndaelEncryptionService("should-be-ignored", usedKey, unusedExpiredKeys); var exception = Assert.Throws(() => service2.Decrypt(encryptedValue, null)); Assert.AreEqual("Could not decrypt message. Tried 2 keys.", exception.Message); @@ -72,7 +79,7 @@ public void Should_throw_when_decrypt_with_wrong_key() public void Should_throw_for_invalid_key() { var invalidKey = Encoding.ASCII.GetBytes("invalidKey"); - var exception = Assert.Throws(() => new RijndaelEncryptionServiceMock("keyid", invalidKey, new List())); + var exception = Assert.Throws(() => new TestableRijndaelEncryptionService("keyid", invalidKey, new List())); Assert.AreEqual("The encryption key has an invalid length of 10 bytes.", exception.Message); } @@ -80,8 +87,11 @@ public void Should_throw_for_invalid_key() public void Should_throw_for_invalid_expired_key() { var validKey = Encoding.ASCII.GetBytes("adDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var expiredKeys = new List { Encoding.ASCII.GetBytes("invalidKey") }; - var exception = Assert.Throws(() => new RijndaelEncryptionServiceMock("keyid", validKey, expiredKeys)); + var expiredKeys = new List + { + Encoding.ASCII.GetBytes("invalidKey") + }; + var exception = Assert.Throws(() => new TestableRijndaelEncryptionService("keyid", validKey, expiredKeys)); Assert.AreEqual("The expired key at index 0 has an invalid length of 10 bytes.", exception.Message); } @@ -89,7 +99,7 @@ public void Should_throw_for_invalid_expired_key() public void Encrypt_must_set_header() { var encryptionKey1 = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service1 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey1, new List()); + var service1 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey1, new List()); Assert.AreEqual(false, service1.OutgoingKeyIdentifierSet); service1.Encrypt("string to encrypt", null); @@ -100,11 +110,14 @@ public void Encrypt_must_set_header() public void Decrypt_using_key_identifier() { var encryptionKey1 = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service1 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey1, new List()); + var service1 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey1, new List()); var encryptedValue = service1.Encrypt("string to encrypt", null); - var expiredKeys = new List { Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6") }; - var service2 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey1, expiredKeys) + var expiredKeys = new List + { + Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6") + }; + var service2 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey1, expiredKeys) { IncomingKeyIdentifier = "encryptionKey1" }; @@ -113,32 +126,34 @@ public void Decrypt_using_key_identifier() Assert.AreEqual("string to encrypt", decryptedValue); } - [Test] public void Decrypt_using_missing_key_identifier_must_throw() { var encryptionKey1 = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service1 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey1, new List()); + var service1 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey1, new List()); var encryptedValue = service1.Encrypt("string to encrypt", null); var encryptionKey2 = Encoding.ASCII.GetBytes("vznkynwuvateefgduvsqjsufqfrrfcya"); - var expiredKeys = new List { Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6") }; - var service2 = new RijndaelEncryptionServiceMock("encryptionKey1", encryptionKey2, expiredKeys) + var expiredKeys = new List + { + Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6") + }; + var service2 = new TestableRijndaelEncryptionService("encryptionKey1", encryptionKey2, expiredKeys) { IncomingKeyIdentifier = "missingKey" }; - Assert.Catch(() => - { - service2.Decrypt(encryptedValue, null); - }, "Decryption key not available for key identifier 'missingKey'. Please add this key to the rijndael encryption service configuration. Key identifiers are case sensitive."); + Assert.Catch(() => { service2.Decrypt(encryptedValue, null); }, "Decryption key not available for key identifier 'missingKey'. Add this key to the rijndael encryption service configuration. Key identifiers are case sensitive."); } [Test] public void Encrypt_using_missing_key_identifier_must_throw() { var encryptionKey1 = Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6"); - var service1 = new RijndaelEncryptionServiceMock(null, encryptionKey1, new List()); + var service1 = new RijndaelEncryptionService(null, new Dictionary + { + {"some-key", encryptionKey1} + }, new List()); Assert.Catch(() => service1.Encrypt("string to encrypt", null), "It is required to set the rijndael key identifier."); } @@ -146,60 +161,66 @@ public void Encrypt_using_missing_key_identifier_must_throw() [Test] public void Should_throw_when_passing_non_existing_key_identifier() { - var encryptionKey1 = Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - - Assert.Catch(() => - { - new RijndaelEncryptionServiceMock("not-in-keys", encryptionKey1, null, new Dictionary()); - }); + Assert.Catch(() => { new RijndaelEncryptionService("not-in-keys", new Dictionary(), null); }); } [Test] public void Should_throw_informative_exception_when_decryption_fails_with_key_identifier() { - var keyIdentier = "encryptionKey1"; + var keyIdentifier = "encryptionKey1"; var key1 = Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - var service1 = new RijndaelEncryptionServiceMock(keyIdentier, key1, new List()); + var service1 = new TestableRijndaelEncryptionService(keyIdentifier, key1, new List()); var encryptedValue = service1.Encrypt("string to encrypt", null); var key2 = Encoding.ASCII.GetBytes("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); - var service2 = new RijndaelEncryptionServiceMock(keyIdentier, key2, new List()) + var service2 = new TestableRijndaelEncryptionService(keyIdentifier, key2, new List()) { IncomingKeyIdentifier = "encryptionKey1" }; - Assert.Catch(() => - { - service2.Decrypt(encryptedValue, null); - }, "Unable to decrypt property using configured decryption key specified in key identifier header."); + Assert.Catch(() => { service2.Decrypt(encryptedValue, null); }, "Unable to decrypt property using configured decryption key specified in key identifier header."); } - class RijndaelEncryptionServiceMock : RijndaelEncryptionService + class TestableRijndaelEncryptionService : RijndaelEncryptionService { - public bool OutgoingKeyIdentifierSet { get; private set; } - public string IncomingKeyIdentifier { private get; set; } - - public RijndaelEncryptionServiceMock( + public TestableRijndaelEncryptionService( string encryptionKeyIdentifier, byte[] encryptionKey, - IList expiredKeys, - IDictionary keys = null - ) - : base(encryptionKeyIdentifier, encryptionKeyIdentifier != null ? keys ?? new Dictionary { { encryptionKeyIdentifier, encryptionKey } } : null, expiredKeys) + IList decryptionKeys) + : base(encryptionKeyIdentifier, new Dictionary + { + {encryptionKeyIdentifier, encryptionKey} + }, decryptionKeys) { } - protected override void AddKeyIdentifierHeader(OutgoingContext outgoingContext) + public bool OutgoingKeyIdentifierSet { get; private set; } + public string IncomingKeyIdentifier { private get; set; } + public byte[] EncryptionIV { get; set; } + + protected override void AddKeyIdentifierHeader(IOutgoingLogicalMessageContext context) { OutgoingKeyIdentifierSet = true; } - protected override bool TryGetKeyIdentifierHeader(out string keyIdentifier, IncomingContext incomingContext) + protected override bool TryGetKeyIdentifierHeader(out string keyIdentifier, IIncomingLogicalMessageContext context) { keyIdentifier = IncomingKeyIdentifier; return IncomingKeyIdentifier != null; } + + protected override void ConfigureIV(RijndaelManaged rijndael) + { + if (EncryptionIV != null) + { + rijndael.IV = EncryptionIV; + } + else + { + base.ConfigureIV(rijndael); + } + } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/ValidationFixture.cs b/src/NServiceBus.Core.Tests/Encryption/ValidationFixture.cs deleted file mode 100644 index 83e0c82eac9..00000000000 --- a/src/NServiceBus.Core.Tests/Encryption/ValidationFixture.cs +++ /dev/null @@ -1,166 +0,0 @@ -namespace NServiceBus.Core.Tests.Encryption -{ - using System.Configuration; - using NServiceBus.Config; - using NUnit.Framework; - - [TestFixture] - public class ValidationFixture - { - [Test] - public void Should_be_false_when_encryption_key_not_in_expired_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - Key = "Key", - ExpiredKeys = new RijndaelExpiredKeyCollection - { - new RijndaelExpiredKey{Key="AnotherKey"}, - } - }; - - Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section)); - } - - [Test] - public void Should_be_true_when_encryption_key_in_expirede_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - Key = "Key", - ExpiredKeys = new RijndaelExpiredKeyCollection - { - new RijndaelExpiredKey{Key="Key"}, - } - }; - - Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section)); - } - - - [Test] - public void Should_be_false_when_no_duplicate_key_identifiers() - { - var section = new RijndaelEncryptionServiceConfig - { - KeyIdentifier = "2", - ExpiredKeys = - { - new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1" }, - new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;4" } - } - }; - - Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); - } - - [Test] - public void Should_be_true_when_duplicate_key_identifier_in_concat_root_and_expired_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - KeyIdentifier = "4;2", - ExpiredKeys = - { - new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1" }, - new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;2" } - } - }; - - Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); - } - [Test] - public void Should_be_true_when_duplicate_key_identifer_in_concat_expired_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - KeyIdentifier = "2", - ExpiredKeys = - { - new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1;4" }, - new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;4" } - } - }; - - Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); - } - - [Test] - public void Should_throw_when_expired_keys_has_duplicate_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - ExpiredKeys = - { - new RijndaelExpiredKey{ Key = "Key" }, - } - }; - - Assert.Throws(() => - { - section.ExpiredKeys.Add(new RijndaelExpiredKey - { - Key = "Key", - KeyIdentifier = "ID" - }); - }, "The entry 'Key' has already been added."); - } - - - [Test] - public void Should_be_false_when_key_has_no_whitespace() - { - var section = new RijndaelEncryptionServiceConfig - { - ExpiredKeys = - { - new RijndaelExpiredKey{ Key = "Key" } - } - }; - - Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveWhiteSpace(section)); - } - [Test] - public void Should_be_true_when_key_has_whitespace() - { - var section = new RijndaelEncryptionServiceConfig - { - ExpiredKeys = - { - new RijndaelExpiredKey{ Key = " " } - } - }; - - Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveWhiteSpace(section)); - } - - [Test] - public void Should_be_false_when_key_identifier_in_expired_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - ExpiredKeys = - { - new RijndaelExpiredKey{ KeyIdentifier = "ID", Key = "Key" } - } - }; - - Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section)); - } - - [Test] - public void Should_be_true_when_key_identifier_not_in_expired_keys() - { - var section = new RijndaelEncryptionServiceConfig - { - ExpiredKeys = - { - new RijndaelExpiredKey { Key = "Key" } - } - }; - - Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section)); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/ValidationTests.cs b/src/NServiceBus.Core.Tests/Encryption/ValidationTests.cs new file mode 100644 index 00000000000..7e040d1b879 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Encryption/ValidationTests.cs @@ -0,0 +1,166 @@ +namespace NServiceBus.Core.Tests.Encryption +{ + using System.Configuration; + using NServiceBus.Config; + using NUnit.Framework; + + [TestFixture] + public class ValidationTests + { + [Test] + public void Should_be_false_when_encryption_key_not_in_expired_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + Key = "Key", + ExpiredKeys = new RijndaelExpiredKeyCollection + { + new RijndaelExpiredKey{Key="AnotherKey"}, + } + }; + + Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section)); + } + + [Test] + public void Should_be_true_when_encryption_key_in_expirede_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + Key = "Key", + ExpiredKeys = new RijndaelExpiredKeyCollection + { + new RijndaelExpiredKey{Key="Key"}, + } + }; + + Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section)); + } + + + [Test] + public void Should_be_false_when_no_duplicate_key_identifiers() + { + var section = new RijndaelEncryptionServiceConfig + { + KeyIdentifier = "2", + ExpiredKeys = + { + new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1" }, + new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;4" } + } + }; + + Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); + } + + [Test] + public void Should_be_true_when_duplicate_key_identifier_in_concat_root_and_expired_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + KeyIdentifier = "4;2", + ExpiredKeys = + { + new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1" }, + new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;2" } + } + }; + + Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); + } + [Test] + public void Should_be_true_when_duplicate_key_identifier_in_concat_expired_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + KeyIdentifier = "2", + ExpiredKeys = + { + new RijndaelExpiredKey{ Key="A", KeyIdentifier = "1;4" }, + new RijndaelExpiredKey{ Key="B", KeyIdentifier = "3;4" } + } + }; + + Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)); + } + + [Test] + public void Should_throw_when_expired_keys_has_duplicate_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + ExpiredKeys = + { + new RijndaelExpiredKey{ Key = "Key" }, + } + }; + + Assert.Throws(() => + { + section.ExpiredKeys.Add(new RijndaelExpiredKey + { + Key = "Key", + KeyIdentifier = "ID" + }); + }, "The entry 'Key' has already been added."); + } + + + [Test] + public void Should_be_false_when_key_has_no_whitespace() + { + var section = new RijndaelEncryptionServiceConfig + { + ExpiredKeys = + { + new RijndaelExpiredKey{ Key = "Key" } + } + }; + + Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveWhiteSpace(section)); + } + [Test] + public void Should_be_true_when_key_has_whitespace() + { + var section = new RijndaelEncryptionServiceConfig + { + ExpiredKeys = + { + new RijndaelExpiredKey{ Key = " " } + } + }; + + Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveWhiteSpace(section)); + } + + [Test] + public void Should_be_false_when_key_identifier_in_expired_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + ExpiredKeys = + { + new RijndaelExpiredKey{ KeyIdentifier = "ID", Key = "Key" } + } + }; + + Assert.IsFalse(RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section)); + } + + [Test] + public void Should_be_true_when_key_identifier_not_in_expired_keys() + { + var section = new RijndaelEncryptionServiceConfig + { + ExpiredKeys = + { + new RijndaelExpiredKey { Key = "Key" } + } + }; + + Assert.IsTrue(RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section)); + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/When_decrypting_a_member_that_is_missing_encryption_data.cs b/src/NServiceBus.Core.Tests/Encryption/When_decrypting_a_member_that_is_missing_encryption_data.cs new file mode 100644 index 00000000000..3480e196cdd --- /dev/null +++ b/src/NServiceBus.Core.Tests/Encryption/When_decrypting_a_member_that_is_missing_encryption_data.cs @@ -0,0 +1,28 @@ +namespace NServiceBus.Core.Tests.Encryption +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class WireEncryptedStringTests + { + [Test] + public void Should_throw_an_exception() + { + var svc = new FakeEncryptionService(new EncryptedValue + { + EncryptedBase64Value = "EncryptedBase64Value", + Base64Iv = "Base64Iv" + }); + + var value = new WireEncryptedString + { + Value = "The real value" + }; + + // ReSharper disable once InvokeAsExtensionMethod + var exception = Assert.Throws(() => WireEncryptedStringConversions.DecryptValue(svc, value, null)); + Assert.AreEqual("Encrypted property is missing encryption data", exception.Message); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/When_message_contains_props_and_fields_that_cannot_be_set.cs b/src/NServiceBus.Core.Tests/Encryption/When_message_contains_props_and_fields_that_cannot_be_set.cs index a06eb1e265f..13f811442a8 100644 --- a/src/NServiceBus.Core.Tests/Encryption/When_message_contains_props_and_fields_that_cannot_be_set.cs +++ b/src/NServiceBus.Core.Tests/Encryption/When_message_contains_props_and_fields_that_cannot_be_set.cs @@ -11,8 +11,7 @@ public void Should_ignore_those_properties_and_fields() { var message = new BogusEntityMessage{ Entity = new BogusEntity()}; - Assert.DoesNotThrow(() => mutator.MutateIncoming(message, null)); - Assert.DoesNotThrow(() => mutator.MutateOutgoing(message, null)); + Assert.IsEmpty(inspector.ScanObject(message)); } public class BogusEntityMessage : IMessage @@ -23,23 +22,16 @@ public class BogusEntityMessage : IMessage public class BogusEntity { //This field generates a stackoverflow - readonly string foo; public BogusEntity() { - foo = "Foo"; + ExposesReadOnlyField = "Foo"; } - public string ExposesReadOnlyField { get { return foo; } } + public string ExposesReadOnlyField { get; private set; } //This property generates a stackoverflow - public List ExposesGetOnlyProperty - { - get - { - return new List { new BogusEntity() }; - } - } + public List ExposesGetOnlyProperty => new List { new BogusEntity() }; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/When_sending_a_message_with_2x_compatibility_disabled.cs b/src/NServiceBus.Core.Tests/Encryption/When_sending_a_message_with_2x_compatibility_disabled.cs new file mode 100644 index 00000000000..01188993198 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Encryption/When_sending_a_message_with_2x_compatibility_disabled.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Core.Tests.Encryption +{ + using NUnit.Framework; + + [TestFixture] + public class When_sending_a_message_with_2x_compatibility_disabled : WireEncryptedStringContext + { + [Test] + public void Should_clear_the_compatibility_properties() + { + var svc = new FakeEncryptionService(new EncryptedValue + { + EncryptedBase64Value = EncryptedBase64Value, + Base64Iv = "Base64Iv" + }); + + var value = (WireEncryptedString) MySecretMessage; + + WireEncryptedStringConversions.EncryptValue(svc, value, null); + Assert.AreEqual(value.EncryptedValue.EncryptedBase64Value, EncryptedBase64Value); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Encryption/WireEncryptedStringSpecs.cs b/src/NServiceBus.Core.Tests/Encryption/WireEncryptedStringSpecs.cs index 12dfe035af4..ed373b1fe6b 100644 --- a/src/NServiceBus.Core.Tests/Encryption/WireEncryptedStringSpecs.cs +++ b/src/NServiceBus.Core.Tests/Encryption/WireEncryptedStringSpecs.cs @@ -3,12 +3,11 @@ using System; using System.Collections; using System.Collections.Generic; - using NServiceBus.Encryption; + using System.Linq; using NUnit.Framework; - using Conventions = NServiceBus.Conventions; [TestFixture] - public class When_sending_a_message_using_the_default_convention : WireEncryptedStringContext + public class When_inspecting_a_message_using_the_default_convention : WireEncryptedStringContext { [Test] public void Should_use_the_wireEncrypted_string() @@ -28,7 +27,10 @@ public void Should_use_the_wireEncrypted_string() }; message.ListOfSecrets = new ArrayList(message.ListOfCreditCards); - mutator.MutateOutgoing(message, null); + var result = inspector.ScanObject(message).ToList(); + result.ForEach(x => x.Item2.SetValue(x.Item1, Create())); + + Assert.AreEqual(5, result.Count); Assert.AreEqual(EncryptedBase64Value, message.Secret.EncryptedValue.EncryptedBase64Value); Assert.AreEqual(EncryptedBase64Value, message.SecretField.EncryptedValue.EncryptedBase64Value); @@ -42,29 +44,28 @@ public void Should_use_the_wireEncrypted_string() } [TestFixture] - public class When_encrypting_a_message_with_indexed_properties : WireEncryptedStringContext + public class When_inspecting_a_message_with_indexed_properties : WireEncryptedStringContext { [Test] - public void Should_encrypt_the_property_correctly() + public void Should_match_the_property_correctly() { var message = new MessageWithIndexedProperties - { - Secret = MySecretMessage - }; + { + Secret = Create() + }; message[0] = "boo"; message[1] = "foo"; - mutator.MutateOutgoing(message, null); + var result = inspector.ScanObject(message).ToList(); - Assert.AreEqual("boo", message[0]); - Assert.AreEqual("foo", message[1]); - Assert.AreEqual(EncryptedBase64Value, message.Secret.EncryptedValue.EncryptedBase64Value); + Assert.AreEqual(1, result.Count); + Assert.AreSame(message.Secret, result[0].Item2.GetValue(result[0].Item1)); } public class MessageWithIndexedProperties : IMessage { - private readonly string[] indexedList = new string[2]; + string[] indexedList = new string[2]; public string this[int index] { @@ -77,23 +78,23 @@ public string this[int index] } [TestFixture] - public class When_encrypting_a_message_with_WireEncryptedString_as_an_indexed_properties : WireEncryptedStringContext + public class When_inspecting_a_message_with_WireEncryptedString_as_an_indexed_properties : WireEncryptedStringContext { [Test] public void Should_throw_exception() { var message = new MessageWithIndexedProperties(); - message[0] = MySecretMessage; - message[1] = MySecretMessage; + message[0] = Create(); + message[1] = Create(); - var exception = Assert.Throws(() => mutator.MutateOutgoing(message, null)); + var exception = Assert.Throws(() => inspector.ScanObject(message).ToList()); Assert.AreEqual("Cannot encrypt or decrypt indexed properties that return a WireEncryptedString.", exception.Message); } public class MessageWithIndexedProperties : IMessage { - private readonly WireEncryptedString[] indexedList = new WireEncryptedString[2]; + WireEncryptedString[] indexedList = new WireEncryptedString[2]; public WireEncryptedString this[int index] { @@ -104,10 +105,10 @@ public WireEncryptedString this[int index] } [TestFixture] - public class When_encrypting_a_message_with_circular_references : WireEncryptedStringContext + public class When_inspecting_a_message_with_circular_references : WireEncryptedStringContext { [Test] - public void Should_encrypt_the_property_correctly() + public void Should_match_the_property_correctly() { var child = new SubProperty {Secret = MySecretMessage}; @@ -118,52 +119,18 @@ public void Should_encrypt_the_property_correctly() child.Self = child; child.Parent = message; - mutator.MutateOutgoing(message, null); - - Assert.AreEqual(EncryptedBase64Value, message.Child.Secret.EncryptedValue.EncryptedBase64Value); - } - } - - [TestFixture] - public class When_decrypting_a_message_with_indexed_properties : WireEncryptedStringContext - { - [Test] - public void Should_decrypt_the_property_correctly() - { - var message = new MessageWithIndexProperties - { - Secret = Create() - }; - - message[0] = "boo"; - message[1] = "foo"; - - mutator.MutateIncoming(message, null); - - Assert.AreEqual("boo", message[0]); - Assert.AreEqual("foo", message[1]); - Assert.AreEqual(MySecretMessage, message.Secret.Value); - } - - public class MessageWithIndexProperties : IMessage - { - private readonly string[] indexedList = new string[2]; - - public string this[int index] - { - get { return indexedList[index]; } - set { indexedList[index] = value; } - } + var result = inspector.ScanObject(message).ToList(); - public WireEncryptedString Secret { get; set; } + Assert.AreEqual(1, result.Count); + Assert.AreEqual("Secret", result[0].Item2.Name); } } [TestFixture] - public class When_decrypting_a_message_with_property_with_backing_public_field : WireEncryptedStringContext + public class When_inspecting_a_message_with_property_with_backing_public_field : WireEncryptedStringContext { [Test] - public void Should_decrypt_the_property_correctly() + public void Should_match_the_property_correctly() { var message = new MessageWithPropertyWithBackingPublicField { @@ -171,7 +138,10 @@ public void Should_decrypt_the_property_correctly() }; - mutator.MutateIncoming(message, null); + inspector + .ScanObject(message) + .ToList() + .ForEach(x => x.Item2.SetValue(x.Item1, (WireEncryptedString)MySecretMessage)); Assert.AreEqual(MySecretMessage, message.MySecret.Value); } @@ -189,74 +159,20 @@ public WireEncryptedString MySecret } [TestFixture] - public class When_decrypting_a_message_with_circular_references : WireEncryptedStringContext - { - [Test] - public void Should_decrypt_the_property_correctly() - { - var child = new SubProperty {Secret = Create()}; - - var message = new MessageWithCircularReferences - { - Child = child - }; - child.Self = child; - child.Parent = message; - - mutator.MutateIncoming(message, null); - - Assert.AreEqual(message.Child.Secret.Value, MySecretMessage); - - - } - } - - [TestFixture] - public class When_decrypting_a_member_that_is_missing_encryption_data : WireEncryptedStringContext - { - [Test] - public void Should_throw_an_exception() - { - - var message = new MessageWithMissingData - { - Secret = new WireEncryptedString {Value = "The real value"} - }; - - var exception = Assert.Throws(() => mutator.MutateIncoming(message, null)); - Assert.AreEqual("Encrypted property is missing encryption data", exception.Message); - } - } - - [TestFixture] - public class When_receiving_a_message_with_a_encrypted_property_that_has_a_nonpublic_setter : + public class When_inspecting_a_message_with_a_encrypted_property_that_has_a_nonpublic_setter : WireEncryptedStringContext { [Test] public void Should_decrypt_correctly() { var message = new SecureMessageWithProtectedSetter(Create()); - mutator.MutateIncoming(message, null); - - Assert.AreEqual(message.Secret.Value, MySecretMessage); - } - } - [TestFixture] - public class When_sending_a_message_with_2x_compatibility_disabled : WireEncryptedStringContext - { - [Test] - public void Should_clear_the_compatibility_properties() - { - var message = new Customer - { - Secret = MySecretMessage - }; - mutator.MutateOutgoing(message, null); + inspector + .ScanObject(message) + .ToList() + .ForEach(x => x.Item2.SetValue(x.Item1, Create())); - Assert.AreEqual(message.Secret.EncryptedValue.EncryptedBase64Value, EncryptedBase64Value); - Assert.AreEqual(message.Secret.EncryptedBase64Value, null); - Assert.AreEqual(message.Secret.Base64Iv, null); + Assert.AreEqual(message.Secret.Value, MySecretMessage); } } @@ -272,8 +188,12 @@ public void Should_use_the_wireEncrypted_string() SecretField = Create(), CreditCard = new CreditCardDetails {CreditCardNumber = Create()} - }; - mutator.MutateIncoming(message, null); + }; + + inspector + .ScanObject(message) + .ToList() + .ForEach(x => x.Item2.SetValue(x.Item1, Create())); Assert.AreEqual(MySecretMessage, message.Secret.Value); Assert.AreEqual(MySecretMessage, message.SecretField.Value); @@ -283,7 +203,7 @@ public void Should_use_the_wireEncrypted_string() public class WireEncryptedStringContext { - internal EncryptionMutator mutator; + internal EncryptionInspector inspector; protected string EncryptedBase64Value = "encrypted value"; protected string MySecretMessage = "A secret"; @@ -293,14 +213,7 @@ public class WireEncryptedStringContext public void BaseSetUp() { conventions = BuildConventions(); - - var encryptedValue = new EncryptedValue - { - EncryptedBase64Value = EncryptedBase64Value, - Base64Iv = "init_vector" - }; - var fakeEncryptionService = new FakeEncryptionService(encryptedValue); - mutator = new EncryptionMutator(fakeEncryptionService, conventions); + inspector = new EncryptionInspector(conventions); } protected virtual Conventions BuildConventions() @@ -314,13 +227,14 @@ protected virtual Conventions BuildConventions() protected WireEncryptedString Create() { return new WireEncryptedString + { + EncryptedValue = new EncryptedValue { - EncryptedValue = new EncryptedValue - { - EncryptedBase64Value = EncryptedBase64Value, - Base64Iv = "init_vector" - } - }; + EncryptedBase64Value = EncryptedBase64Value, + Base64Iv = "init_vector" + }, + Value = MySecretMessage + }; } } diff --git a/src/NServiceBus.Core.Tests/EndpointConfigurationTests.cs b/src/NServiceBus.Core.Tests/EndpointConfigurationTests.cs new file mode 100644 index 00000000000..a4ccddf7044 --- /dev/null +++ b/src/NServiceBus.Core.Tests/EndpointConfigurationTests.cs @@ -0,0 +1,28 @@ +namespace NServiceBus.Core.Tests +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class EndpointConfigurationTests + { + [Theory] + [TestCase("")] + [TestCase(" ")] + [TestCase(null)] + public void When_creating_configuration_with_empty_endpoint_name_should_throw(string name) + { + var exception = Assert.Throws(() => new EndpointConfiguration(name)); + + Assert.That(exception.Message, Does.Contain("Endpoint name must not be empty").And.Contain("Parameter name: endpointName")); + } + + [Test] + public void When_creating_configuration_with_invalid_character_in_endpoint_name_should_throw() + { + var exception = Assert.Throws(() => new EndpointConfiguration("endpoint@V6")); + + Assert.That(exception.Message, Does.Contain("Endpoint name must not contain an '@' character.").And.Contain("Parameter name: endpointName")); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ExceptionTests.cs b/src/NServiceBus.Core.Tests/ExceptionTests.cs index 9e99c386ad8..ef921b339f2 100644 --- a/src/NServiceBus.Core.Tests/ExceptionTests.cs +++ b/src/NServiceBus.Core.Tests/ExceptionTests.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Runtime.Serialization; using NUnit.Framework; - using Unicast; [TestFixture] public class ExceptionTests @@ -14,34 +13,36 @@ public class ExceptionTests [Test] public void VerifyExceptionConventions() { - var exceptionTypes = new List(); - - exceptionTypes.AddRange(GetExceptionTypes(typeof(IMessage).Assembly)); - exceptionTypes.AddRange(GetExceptionTypes(typeof(UnicastBus).Assembly)); - - foreach (var exceptionType in exceptionTypes) + foreach (var exceptionType in GetExceptionTypes()) { - if (exceptionType.GetCustomAttribute() !=null ) + if (exceptionType.GetCustomAttribute() != null) + { + continue; + } + if (!exceptionType.IsPublic) { continue; } - Assert.IsTrue(exceptionType.IsPublic, string.Format("Exception '{0}' should be public", exceptionType.Name)); - var constructor = exceptionType.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); + var constructor = exceptionType.GetConstructor(BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] + { + typeof(SerializationInfo), + typeof(StreamingContext) + }, null); Assert.IsNotNull(constructor, string.Format("Exception '{0}' should implement 'protected {0}(SerializationInfo info, StreamingContext context){{}}'", exceptionType.Name)); var serializableAttribute = exceptionType.GetCustomAttributes(typeof(SerializableAttribute), false).FirstOrDefault(); - Assert.IsNotNull(serializableAttribute, string.Format("Exception '{0}' should have a 'SerializableAttribute'", exceptionType.Name)); - var properties = exceptionType.GetProperties(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly); + Assert.IsNotNull(serializableAttribute, $"Exception '{exceptionType.Name}' should have a 'SerializableAttribute'"); + var properties = exceptionType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (properties.Length > 0) { var getObjectDataMethod = exceptionType.GetMethod("GetObjectData"); - Assert.IsTrue(getObjectDataMethod.DeclaringType.Name != "Exception", string.Format("Exception '{0}' has properties and as such should override 'GetObjectData'", exceptionType.Name)); + Assert.IsTrue(getObjectDataMethod.DeclaringType.Name != "Exception", $"Exception '{exceptionType.Name}' has properties and as such should override 'GetObjectData'"); } } } - static IEnumerable GetExceptionTypes(Assembly assembly) + static IEnumerable GetExceptionTypes() { - foreach (var type in assembly.GetTypes()) + foreach (var type in typeof(Endpoint).Assembly.GetTypes()) { if (typeof(Exception).IsAssignableFrom(type) && type.Namespace.StartsWith("NServiceBus")) { diff --git a/src/NServiceBus.Core.Tests/FakeEventAggregator.cs b/src/NServiceBus.Core.Tests/FakeEventAggregator.cs new file mode 100644 index 00000000000..aae899fe99a --- /dev/null +++ b/src/NServiceBus.Core.Tests/FakeEventAggregator.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Core.Tests +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + class FakeEventAggregator : IEventAggregator + { + public FakeEventAggregator() + { + NotificationsRaised = new List(); + } + public Task Raise(T @event) + { + NotificationsRaised.Add(@event); + return TaskEx.CompletedTask; + } + + public T GetNotification() + { + return (T)NotificationsRaised.LastOrDefault(n => n is T); + } + + public List NotificationsRaised { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/FakeBus.cs b/src/NServiceBus.Core.Tests/Fakes/FakeBus.cs deleted file mode 100644 index 39c541d8334..00000000000 --- a/src/NServiceBus.Core.Tests/Fakes/FakeBus.cs +++ /dev/null @@ -1,201 +0,0 @@ -namespace NServiceBus.Core.Tests.Fakes -{ - using System; - using System.Collections.Generic; - using System.Threading; - - public class FakeBus : IBus - { - - public void Publish(T message) - { - throw new NotImplementedException(); - } - - public void Publish() - { - throw new NotImplementedException(); - } - - public void Publish(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public void Subscribe(Type messageType) - { - throw new NotImplementedException(); - } - - public void Subscribe() - { - throw new NotImplementedException(); - } - - public void Unsubscribe(Type messageType) - { - throw new NotImplementedException(); - } - - public void Unsubscribe() - { - throw new NotImplementedException(); - } - - public ICallback SendLocal(object message) - { - throw new NotImplementedException(); - } - - public ICallback SendLocal(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Send(object message) - { - throw new NotImplementedException(); - } - - public ICallback Send(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Send(string destination, object message) - { - throw new NotImplementedException(); - } - - public ICallback Send(Address address, object message) - { - throw new NotImplementedException(); - } - - public ICallback Send(string destination, Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Send(Address address, Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Send(string destination, string correlationId, object message) - { - throw new NotImplementedException(); - } - - public ICallback Send(Address address, string correlationId, object message) - { - throw new NotImplementedException(); - } - - public ICallback Send(string destination, string correlationId, Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback Send(Address address, string correlationId, Action messageConstructor) - { - throw new NotImplementedException(); - } - - public ICallback SendToSites(IEnumerable siteKeys, object message) - { - throw new NotImplementedException(); - } - - public ICallback Defer(TimeSpan delay, object message) - { - return Defer(delay,new []{message}); - } - - public ICallback Defer(TimeSpan delay, params object[] messages) - { - Interlocked.Increment(ref _deferWasCalled); - _deferDelay = delay; - _deferMessages = messages; - return null; - } - - private int _deferWasCalled; - public int DeferWasCalled - { - get { return _deferWasCalled; } - set { _deferWasCalled = value; } - } - private TimeSpan _deferDelay = TimeSpan.MinValue; - public TimeSpan DeferDelay - { - get { return _deferDelay; } - } - private object[] _deferMessages; - public object[] DeferMessages - { - get { return _deferMessages; } - } - - private DateTime _deferProcessAt = DateTime.MinValue; - public DateTime DeferProcessAt - { - get { return _deferProcessAt; } - } - - public ICallback Defer(DateTime processAt, object message) - { - throw new NotImplementedException(); - } - - public void Reply(object message) - { - throw new NotImplementedException(); - } - - public void Reply(Action messageConstructor) - { - throw new NotImplementedException(); - } - - public void Return(T errorEnum) - { - throw new NotImplementedException(); - } - - public void HandleCurrentMessageLater() - { - throw new NotImplementedException(); - } - - public void ForwardCurrentMessageTo(string destination) - { - throw new NotImplementedException(); - } - - public void DoNotContinueDispatchingCurrentMessageToHandlers() - { - throw new NotImplementedException(); - } - - public IDictionary OutgoingHeaders - { - get { throw new NotImplementedException(); } - } - - public IMessageContext CurrentMessageContext - { - get { throw new NotImplementedException(); } - } - - public IInMemoryOperations InMemory - { - get { throw new NotImplementedException(); } - } - - public void Dispose() - { - - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/FakeCentralizedPubSubTransportDefinition.cs b/src/NServiceBus.Core.Tests/Fakes/FakeCentralizedPubSubTransportDefinition.cs deleted file mode 100644 index 94d2a60ae1b..00000000000 --- a/src/NServiceBus.Core.Tests/Fakes/FakeCentralizedPubSubTransportDefinition.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.Core.Tests.Fakes -{ - using Transports; - - public class FakeCentralizedPubSubTransportDefinition : TransportDefinition - { - public FakeCentralizedPubSubTransportDefinition() - { - HasNativePubSubSupport = true; - HasSupportForCentralizedPubSub = true; - } - } -} diff --git a/src/NServiceBus.Core.Tests/Fakes/FakeReceiver.cs b/src/NServiceBus.Core.Tests/Fakes/FakeReceiver.cs deleted file mode 100644 index 69ada889f81..00000000000 --- a/src/NServiceBus.Core.Tests/Fakes/FakeReceiver.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NServiceBus.Core.Tests.Fakes -{ - using System; - using Transports; - using Unicast.Transport; - - public class FakeReceiver : IDequeueMessages - { - public void FakeMessageReceived() - { - FakeMessageReceived(new TransportMessage()); - } - - public void FakeMessageReceived(TransportMessage message) - { - if (TryProcessMessage(message)) - NumberOfMessagesReceived++; - } - - - public void Init(Address address, TransactionSettings transactionSettings, Func tryProcessMessage, Action endProcessMessage) - { - InputAddress = address; - TryProcessMessage = tryProcessMessage; - } - - public void Start(int maximumConcurrencyLevel) - { - IsStarted = true; - } - - public void Stop() - { - - } - - Func TryProcessMessage; - public int NumberOfMessagesReceived; - - public bool IsStarted { get; set; } - - public Address InputAddress { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/FakeTransport.cs b/src/NServiceBus.Core.Tests/Fakes/FakeTransport.cs deleted file mode 100644 index 984a050e3d1..00000000000 --- a/src/NServiceBus.Core.Tests/Fakes/FakeTransport.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NServiceBus.Core.Tests.Fakes -{ - using System; - using Unicast.Transport; - - public class FakeTransport : ITransport - { - public void Dispose() - { - throw new NotImplementedException(); - } - - public void Stop() - { - } - - public bool IsStarted { get; set; } - public Address InputAddress { get; set; } - public void Start(Address localAddress) - { - IsStarted = true; - InputAddress = localAddress; - } - - public int HasChangedMaximumConcurrencyLevelNTimes { get; set; } - - public int MaximumConcurrencyLevel { get; private set; } - - public void ChangeMaximumConcurrencyLevel(int maximumConcurrencyLevel) - { - MaximumConcurrencyLevel = maximumConcurrencyLevel; - HasChangedMaximumConcurrencyLevelNTimes++; - } - - public void AbortHandlingCurrentMessage() - { - throw new NotImplementedException(); - } - - public int MaximumMessageThroughputPerSecond { get; private set; } - - public bool IsEventAssigned - { - get { return TransportMessageReceived != null; } - } - - public void RaiseEvent(TransportMessage message) - { - TransportMessageReceived(this, new TransportMessageReceivedEventArgs(message)); - } - - public void ChangeMaximumMessageThroughputPerSecond(int maximumMessageThroughputPerSecond) - { - throw new NotImplementedException(); - } - - public event EventHandler TransportMessageReceived; - public event EventHandler StartedMessageProcessing; - public event EventHandler FinishedMessageProcessing; - public event EventHandler FailedMessageProcessing; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/TestableContextChecker.cs b/src/NServiceBus.Core.Tests/Fakes/TestableContextChecker.cs new file mode 100644 index 00000000000..a7a320296fa --- /dev/null +++ b/src/NServiceBus.Core.Tests/Fakes/TestableContextChecker.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Testing.Tests.Fakes +{ + using System.Linq; + using System.Reflection; + using Pipeline; + using NUnit.Framework; + + [TestFixture] + public class TestableContextChecker + { + [Test] + public void ShouldProvideTestableImplementationForAllBehaviorContexts() + { + var nservicebusAssembly = Assembly.GetAssembly(typeof(IMessageHandlerContext)); + var testingAssembly = Assembly.GetAssembly(typeof(TestableMessageSession)); + var behaviorContextType = typeof(IBehaviorContext); + + var behaviorContextInterfaces = nservicebusAssembly.DefinedTypes + .Where(x => x.IsInterface && behaviorContextType.IsAssignableFrom(x)) + .Except(new[] + { + typeof(PipelineTerminator<>.ITerminatingContext) + }); + + foreach (var behaviorContextInterface in behaviorContextInterfaces) + { + var testableImplementationName = "Testable" + behaviorContextInterface.Name.Substring(1); + + if (!testingAssembly.DefinedTypes.Any(t => t.Name == testableImplementationName && behaviorContextInterface.IsAssignableFrom(t))) + { + Assert.Fail($"Found no testable implementation for {behaviorContextInterface.FullName}. Expecting an implementation named {testableImplementationName}."); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/TestableMessageHandlerContextTests.cs b/src/NServiceBus.Core.Tests/Fakes/TestableMessageHandlerContextTests.cs new file mode 100644 index 00000000000..fe1f51a80f8 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Fakes/TestableMessageHandlerContextTests.cs @@ -0,0 +1,152 @@ +namespace NServiceBus.Testing.Tests.Fakes +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NUnit.Framework; + + [TestFixture] + public class TestableMessageHandlerContextTests + { + [Test] + public async Task Send_ShouldContainMessageInSentMessages() + { + var context = new TestableMessageHandlerContext(); + var messageInstance = new TestMessage(); + var sendOptions = new SendOptions(); + + await context.Send(messageInstance, sendOptions); + + Assert.AreEqual(1, context.SentMessages.Length); + Assert.AreSame(messageInstance, context.SentMessages[0].Message); + Assert.AreSame(sendOptions, context.SentMessages[0].Options); + } + + [Test] + public async Task Send_ShouldInvokeMessageInitializer() + { + var context = new TestableMessageHandlerContext(); + + await context.Send(m => m.Value = "initialized value"); + + Assert.AreEqual("initialized value", context.SentMessages[0].Message().Value); + } + + [Test] + public async Task Publish_ShouldContainMessageInPublishedMessages() + { + var context = new TestableMessageHandlerContext(); + var messageInstance = new TestMessage(); + var publishOptions = new PublishOptions(); + + await context.Publish(messageInstance, publishOptions); + + Assert.AreEqual(1, context.PublishedMessages.Length); + Assert.AreSame(messageInstance, context.PublishedMessages[0].Message); + Assert.AreSame(publishOptions, context.PublishedMessages[0].Options); + } + + [Test] + public async Task Publish_ShouldInvokeMessageInitializer() + { + var context = new TestableMessageHandlerContext(); + + await context.Publish(m => m.Value = "initialized value"); + + Assert.AreEqual("initialized value", context.PublishedMessages[0].Message().Value); + } + + [Test] + public async Task Reply_ShouldContainMessageInRepliedMessages() + { + var context = new TestableMessageHandlerContext(); + var messageInstance = new TestMessage(); + var publishOptions = new ReplyOptions(); + + await context.Reply(messageInstance, publishOptions); + + Assert.AreEqual(1, context.RepliedMessages.Length); + Assert.AreSame(messageInstance, context.RepliedMessages[0].Message); + Assert.AreSame(publishOptions, context.RepliedMessages[0].Options); + } + + [Test] + public async Task Reply_ShouldInvokeMessageInitializer() + { + var context = new TestableMessageHandlerContext(); + + await context.Reply(m => m.Value = "initialized value"); + + Assert.AreEqual("initialized value", context.RepliedMessages[0].Message().Value); + } + + [Test] + public async Task ForwardCurrentMessageTo_ShouldContainDestinationsInForwardDestinations() + { + var context = new TestableMessageHandlerContext(); + + await context.ForwardCurrentMessageTo("destination1"); + await context.ForwardCurrentMessageTo("destination2"); + + Assert.Contains("destination1", context.ForwardedMessages); + Assert.Contains("destination2", context.ForwardedMessages); + } + + [Test] + public void DoNotContinueDispatchingCurrentMessageToHandlers_WhenNotCalled_ShouldNotIndicateInvocation() + { + var context = new TestableMessageHandlerContext(); + + Assert.IsFalse(context.DoNotContinueDispatchingCurrentMessageToHandlersWasCalled); + } + + [Test] + public void DoNotContinueDispatchingCurrentMessageToHandlers_WhenCalled_ShouldIndicateInvocation() + { + var context = new TestableMessageHandlerContext(); + + context.DoNotContinueDispatchingCurrentMessageToHandlers(); + + Assert.IsTrue(context.DoNotContinueDispatchingCurrentMessageToHandlersWasCalled); + } + + [Test] + public void HandleCurrentMessageLater_WhenNotCalled_ShouldNotIndicateInvocation() + { + var context = new TestableMessageHandlerContext(); + + Assert.IsFalse(context.HandleCurrentMessageLaterWasCalled); + } + + [Test] + public void HandleCurrentMessageLater_WhenCalled_ShouldIndicateInvocation() + { + var context = new TestableMessageHandlerContext(); + + context.HandleCurrentMessageLater(); + + Assert.IsTrue(context.HandleCurrentMessageLaterWasCalled); + } + + [Test] + public void ShouldAllowSettingMessageProperties() + { + var context = new TestableMessageHandlerContext(); + + context.MessageId = "custom message id"; + context.ReplyToAddress = "custom reply address"; + context.MessageHeaders = new Dictionary(); + context.MessageHeaders.Add("custom header", "custom value"); + context.Extensions = new ContextBag(); + } + + class TestMessage + { + } + + public interface ITestMessage + { + string Value { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Fakes/TestableMessageSessionTests.cs b/src/NServiceBus.Core.Tests/Fakes/TestableMessageSessionTests.cs new file mode 100644 index 00000000000..c2cd4fe4e92 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Fakes/TestableMessageSessionTests.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.Core.Tests.Fakes +{ + using NUnit.Framework; + using Testing; + + [TestFixture] + public class TestableMessageSessionTests + { + [Test] + public void Subscribe_ShouldTrackSubscriptions() + { + var session = new TestableMessageSession(); + var options = new SubscribeOptions(); + + session.Subscribe(typeof(MyEvent), options); + + Assert.AreEqual(1, session.Subscriptions.Length); + Assert.AreSame(options, session.Subscriptions[0].Options); + Assert.AreEqual(typeof(MyEvent), session.Subscriptions[0].Message); + } + + [Test] + public void Unsubscribe_ShouldTrackUnsubscriptions() + { + var session = new TestableMessageSession(); + var options = new UnsubscribeOptions(); + + session.Unsubscribe(typeof(MyEvent), options); + + Assert.AreEqual(1, session.Unsubscription.Length); + Assert.AreSame(options, session.Unsubscription[0].Options); + Assert.AreEqual(typeof(MyEvent), session.Unsubscription[0].Message); + } + + class MyEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Faults/FaultManagerTests.cs b/src/NServiceBus.Core.Tests/Faults/FaultManagerTests.cs deleted file mode 100644 index 9939156af58..00000000000 --- a/src/NServiceBus.Core.Tests/Faults/FaultManagerTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace NServiceBus.Core.Tests.Faults -{ - using System; - using System.Collections.Generic; - using NServiceBus.Faults; - using NServiceBus.Faults.Forwarder; - using NServiceBus.ObjectBuilder; - using NServiceBus.ObjectBuilder.Common; - using NServiceBus.SecondLevelRetries; - using NServiceBus.Settings; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NUnit.Framework; - - [TestFixture] - public class FaultManagerTests - { - private IManageMessageFailures faultManager; - private FakeSender fakeSender; - - [SetUp] - public void SetUp() - { - fakeSender = new FakeSender(); - var settingsHolder = new SettingsHolder(); - faultManager = new FaultManager(fakeSender, new Configure(settingsHolder, new FakeContainer(), new List>(), null), new BusNotifications()); - faultManager.Init(new Address("fake", "fake")); - } - - [Test] - public void SendingToErrorQueue_WhenSerializationFailedForMessage_ShouldRemoveTTBR() - { - var exception = new InvalidOperationException(); - var message = new TransportMessage("id", new Dictionary()); - message.TimeToBeReceived = TimeSpan.FromHours(1); - - faultManager.SerializationFailedForMessage(message, exception); - - var sentMessage = fakeSender.SentMessage; - Assert.AreEqual(TimeSpan.MaxValue, sentMessage.TimeToBeReceived); - } - - [Test] - public void SendingToErrorQueue_ProcessingAlwaysFailsForMessage_WhenSentFromSLR_ShouldRemoveTTBR() - { - var exception = new InvalidOperationException(); - var message = new TransportMessage("id", new Dictionary()); - message.TimeToBeReceived = TimeSpan.FromHours(1); - - ((FaultManager) faultManager).RetriesQueue = new Address("fake", "fake"); - - faultManager.ProcessingAlwaysFailsForMessage(message, exception); - - var sentMessage = fakeSender.SentMessage; - Assert.AreEqual(TimeSpan.MaxValue, sentMessage.TimeToBeReceived); - } - - [Test] - public void SendingToErrorQueue_ProcessingAlwaysFailsForMessage_WhenRetriesQueueIsNull_ShouldRemoveTTBR() - { - var exception = new InvalidOperationException(); - var message = new TransportMessage("id", new Dictionary()); - message.TimeToBeReceived = TimeSpan.FromHours(1); - - faultManager.ProcessingAlwaysFailsForMessage(message, exception); - - var sentMessage = fakeSender.SentMessage; - Assert.AreEqual(TimeSpan.MaxValue, sentMessage.TimeToBeReceived); - } - - [Test] - public void SendingToErrorQueue_ProcessingAlwaysFailsForMessage_WhenPolicyReturnsTimeSpanZeroOrLess_ShouldRemoveTTBR() - { - var exception = new InvalidOperationException(); - var message = new TransportMessage("id", new Dictionary()); - message.TimeToBeReceived = TimeSpan.FromHours(1); - - var manager = (FaultManager)faultManager; - manager.RetriesQueue = new Address("retries", "fake"); - manager.SecondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration - { - RetryPolicy = tm => TimeSpan.MinValue, - }; - - faultManager.ProcessingAlwaysFailsForMessage(message, exception); - - var sentMessage = fakeSender.SentMessage; - Assert.AreEqual(TimeSpan.MaxValue, sentMessage.TimeToBeReceived); - } - - [Test] - public void SendingToRetriesQueue_ProcessingAlwaysFailsForMessage_ShouldRemoveTTBR() - { - var exception = new InvalidOperationException(); - var message = new TransportMessage("id", new Dictionary()); - message.TimeToBeReceived = TimeSpan.FromHours(1); - - var manager = (FaultManager)faultManager; - manager.RetriesQueue = new Address("retries", "fake"); - manager.SecondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration - { - RetryPolicy = tm => TimeSpan.MaxValue, - }; - - faultManager.ProcessingAlwaysFailsForMessage(message, exception); - - var sentMessage = fakeSender.SentMessage; - Assert.AreEqual(TimeSpan.MaxValue, sentMessage.TimeToBeReceived); - } - - class FakeSender : ISendMessages - { - public void Send(TransportMessage message, SendOptions sendOptions) - { - SentMessage = message; - } - - public TransportMessage SentMessage { get; set; } - } - - class FakeContainer : IContainer - { - public void Dispose() - { - } - - public object Build(Type typeToBuild) - { - return null; - } - - public IContainer BuildChildContainer() - { - return null; - } - - public IEnumerable BuildAll(Type typeToBuild) - { - yield break; - } - - public void Configure(Type component, DependencyLifecycle dependencyLifecycle) - { - } - - public void Configure(Func component, DependencyLifecycle dependencyLifecycle) - { - } - - public void ConfigureProperty(Type component, string property, object value) - { - } - - public void RegisterSingleton(Type lookupType, object instance) - { - } - - public bool HasComponent(Type componentType) - { - return true; - } - - public void Release(object instance) - { - } - } - } -} diff --git a/src/NServiceBus.Core.Tests/Features/FeatureDefaultsTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureDefaultsTests.cs index 828b15d2deb..66b7e98b30e 100644 --- a/src/NServiceBus.Core.Tests/Features/FeatureDefaultsTests.cs +++ b/src/NServiceBus.Core.Tests/Features/FeatureDefaultsTests.cs @@ -3,12 +3,64 @@ using System.Collections.Generic; using System.Linq; using NServiceBus.Features; + using Transport; using NUnit.Framework; using Settings; [TestFixture] public class FeatureDefaultsTests { + public class FeatureThatEnablesAnother : Feature + { + public FeatureThatEnablesAnother() + { + EnableByDefault(); + Defaults(s => s.EnableFeatureByDefault()); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + } + } + + public class FeatureThatIsEnabledByAnother : Feature + { + public FeatureThatIsEnabledByAnother() + { + Defaults(s => DefaultCalled = true); + } + + public bool DefaultCalled; + + protected internal override void Setup(FeatureConfigurationContext context) + { + } + } + + private FeatureActivator featureSettings; + private SettingsHolder settings; + + [SetUp] + public void Init() + { + settings = new SettingsHolder(); + settings.Set(new MsmqTransport()); + featureSettings = new FeatureActivator(settings); + } + + [Test] + public void Feature_enabled_by_later_feature_should_have_default_called() + { + var featureThatIsEnabledByAnother = new FeatureThatIsEnabledByAnother(); + //the orders matter here to expose a bug + featureSettings.Add(featureThatIsEnabledByAnother); + featureSettings.Add(new FeatureThatEnablesAnother()); + + featureSettings.SetupFeatures(null, null); + + Assert.True(featureThatIsEnabledByAnother.DefaultCalled, "FeatureThatIsEnabledByAnother wasn't activated"); + } + [Test] public void Should_enable_features_in_defaults() { @@ -31,29 +83,26 @@ public void Should_enable_features_in_defaults() OnDefaults = f => defaultsOrder.Add(f) }; - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - //the orders matter here to expose a bug featureSettings.Add(level3); featureSettings.Add(level2); featureSettings.Add(level1); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(level1.IsActive, "Activate1 wasn't activated"); Assert.True(level2.IsActive, "Activate2 wasn't activated"); Assert.True(level3.IsActive, "Activate3 wasn't activated"); - Assert.IsInstanceOf(defaultsOrder[0], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[1], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[2], "Upstream deps should be activated first"); + Assert.IsInstanceOf(defaultsOrder[0], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[1], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[2], "Upstream dependencies should be activated first"); CollectionAssert.AreEqual(defaultsOrder, activatedOrder); } [Test] - public void Should_activate_upstream_deps_first() + public void Should_activate_upstream_dependencies_first() { var defaultsOrder = new List(); @@ -66,23 +115,20 @@ public void Should_activate_upstream_deps_first() OnDefaults = f => defaultsOrder.Add(f) }; - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - featureSettings.Add(dependingFeature); featureSettings.Add(feature); settings.EnableFeatureByDefault(); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(dependingFeature.IsActive); - Assert.IsInstanceOf(defaultsOrder.First(), "Upstream deps should be activated first"); + Assert.IsInstanceOf(defaultsOrder.First(), "Upstream dependencies should be activated first"); } [Test] - public void Should_activate_all_upstream_deps_first() + public void Should_activate_all_upstream_dependencies_first() { var defaultsOrder = new List(); @@ -103,9 +149,6 @@ public void Should_activate_all_upstream_deps_first() OnDefaults = f => defaultsOrder.Add(f) }; - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - featureSettings.Add(dependingFeature); featureSettings.Add(feature); featureSettings.Add(feature2); @@ -115,17 +158,17 @@ public void Should_activate_all_upstream_deps_first() settings.EnableFeatureByDefault(); settings.EnableFeatureByDefault(); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(dependingFeature.IsActive); - Assert.IsInstanceOf(defaultsOrder[0], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[1], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[2], "Upstream deps should be activated first"); + Assert.IsInstanceOf(defaultsOrder[0], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[1], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[2], "Upstream dependencies should be activated first"); } [Test] - public void Should_activate_all_upstream_deps_when_chain_deep() + public void Should_activate_all_upstream_dependencies_when_chain_deep() { var defaultsOrder = new List(); @@ -142,23 +185,20 @@ public void Should_activate_all_upstream_deps_when_chain_deep() OnDefaults = f => defaultsOrder.Add(f) }; - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - //the orders matter here to expose a bug featureSettings.Add(level3); featureSettings.Add(level2); featureSettings.Add(level1); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(level1.IsActive, "Level1 wasn't activated"); Assert.True(level2.IsActive, "Level2 wasn't activated"); Assert.True(level3.IsActive, "Level3 wasn't activated"); - Assert.IsInstanceOf(defaultsOrder[0], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[1], "Upstream deps should be activated first"); - Assert.IsInstanceOf(defaultsOrder[2], "Upstream deps should be activated first"); + Assert.IsInstanceOf(defaultsOrder[0], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[1], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(defaultsOrder[2], "Upstream dependencies should be activated first"); } public class Level1 : TestFeature diff --git a/src/NServiceBus.Core.Tests/Features/FeatureDependencyTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureDependencyTests.cs index 5938985ea7d..7efdfcf10fe 100644 --- a/src/NServiceBus.Core.Tests/Features/FeatureDependencyTests.cs +++ b/src/NServiceBus.Core.Tests/Features/FeatureDependencyTests.cs @@ -10,7 +10,7 @@ [TestFixture] public class FeatureDependencyTests { - private IEnumerable FeatureCombinationsForTests + static IEnumerable FeatureCombinationsForTests { get { @@ -59,13 +59,13 @@ public void Should_only_activate_features_if_dependencies_are_met(FeatureCombina featureSettings.Add(dependingFeature); Array.ForEach(setup.AvailableFeatures, featureSettings.Add); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.AreEqual(setup.ShouldBeActive, dependingFeature.IsActive); } [Test] - public void Should_activate_upstream_deps_first() + public void Should_activate_upstream_dependencies_first() { var order = new List(); @@ -86,15 +86,69 @@ public void Should_activate_upstream_deps_first() settings.EnableFeatureByDefault(); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(dependingFeature.IsActive); - Assert.IsInstanceOf(order.First(), "Upstream deps should be activated first"); + Assert.IsInstanceOf(order.First(), "Upstream dependencies should be activated first"); } [Test] - public void Should_activate_all_upstream_deps_first() + public void Should_activate_named_dependency_first() + { + var order = new List(); + + var dependingFeature = new DependsOnOneByName_Feature + { + OnActivation = f => order.Add(f) + }; + var feature = new MyFeature2 + { + OnActivation = f => order.Add(f) + }; + + var settings = new SettingsHolder(); + var featureSettings = new FeatureActivator(settings); + + featureSettings.Add(dependingFeature); + featureSettings.Add(feature); + + settings.EnableFeatureByDefault(); + + featureSettings.SetupFeatures(null, null); + + Assert.True(dependingFeature.IsActive); + Assert.IsInstanceOf(order.First(), "Upstream dependencies should be activated first"); + } + + [Test] + public void Should_not_activate_feature_when_named_dependency_disabled() + { + var order = new List(); + + var dependingFeature = new DependsOnOneByName_Feature + { + OnActivation = f => order.Add(f) + }; + var feature = new MyFeature2 + { + OnActivation = f => order.Add(f) + }; + + var settings = new SettingsHolder(); + var featureSettings = new FeatureActivator(settings); + + featureSettings.Add(dependingFeature); + featureSettings.Add(feature); + + featureSettings.SetupFeatures(null, null); + + Assert.False(dependingFeature.IsActive); + Assert.IsEmpty(order); + } + + [Test] + public void Should_activate_all_upstream_dependencies_first() { var order = new List(); @@ -127,21 +181,21 @@ public void Should_activate_all_upstream_deps_first() settings.EnableFeatureByDefault(); settings.EnableFeatureByDefault(); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(dependingFeature.IsActive); - Assert.IsInstanceOf(order[0], "Upstream deps should be activated first"); - Assert.IsInstanceOf(order[1], "Upstream deps should be activated first"); - Assert.IsInstanceOf(order[2], "Upstream deps should be activated first"); + Assert.IsInstanceOf(order[0], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(order[1], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(order[2], "Upstream dependencies should be activated first"); } [Test] - public void Should_activate_all_upstream_deps_when_chain_deep() + public void Should_activate_all_upstream_dependencies_when_chain_deep() { var order = new List(); - + var level1 = new Level1 { OnActivation = f => order.Add(f) @@ -163,16 +217,40 @@ public void Should_activate_all_upstream_deps_when_chain_deep() featureSettings.Add(level2); featureSettings.Add(level1); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(level1.IsActive, "Level1 wasn't activated"); Assert.True(level2.IsActive, "Level2 wasn't activated"); Assert.True(level3.IsActive, "Level3 wasn't activated"); - Assert.IsInstanceOf(order[0], "Upstream deps should be activated first"); - Assert.IsInstanceOf(order[1], "Upstream deps should be activated first"); - Assert.IsInstanceOf(order[2], "Upstream deps should be activated first"); + Assert.IsInstanceOf(order[0], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(order[1], "Upstream dependencies should be activated first"); + Assert.IsInstanceOf(order[2], "Upstream dependencies should be activated first"); + } + + [Test] + public void Should_throw_exception_when_dependency_cycle_is_found() + { + var order = new List(); + + + var level1 = new CycleLevel1 + { + OnActivation = f => order.Add(f) + }; + var level2 = new CycleLevel2 + { + OnActivation = f => order.Add(f) + }; + + var settings = new SettingsHolder(); + var featureSettings = new FeatureActivator(settings); + + featureSettings.Add(level1); + featureSettings.Add(level2); + + Assert.Throws(() => featureSettings.SetupFeatures(null, null)); } public class Level1 : TestFeature @@ -201,6 +279,24 @@ public Level3() } } + public class CycleLevel1 : TestFeature + { + public CycleLevel1() + { + EnableByDefault(); + DependsOn(); + } + } + + public class CycleLevel2 : TestFeature + { + public CycleLevel2() + { + EnableByDefault(); + DependsOn(); + } + } + public class MyFeature1 : TestFeature { @@ -225,6 +321,15 @@ public DependsOnOne_Feature() } } + public class DependsOnOneByName_Feature : TestFeature + { + public DependsOnOneByName_Feature() + { + EnableByDefault(); + DependsOn("NServiceBus.Core.Tests.Features.FeatureDependencyTests+MyFeature2"); + } + } + public class DependsOnAll_Feature : TestFeature { public DependsOnAll_Feature() diff --git a/src/NServiceBus.Core.Tests/Features/FeatureDifferingOnlyByNamespaceTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureDifferingOnlyByNamespaceTests.cs new file mode 100644 index 00000000000..2564bc410d1 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Features/FeatureDifferingOnlyByNamespaceTests.cs @@ -0,0 +1,64 @@ +namespace NServiceBus.Core.Tests.Features +{ + using System.Collections.Generic; + using System.Linq; + using NServiceBus.Features; + using NUnit.Framework; + using Settings; + + [TestFixture] + public class FeatureDifferingOnlyByNamespaceTests + { + [Test] + public void Should_activate_upstream_dependencies_first() + { + var order = new List(); + + var dependingFeature = new NamespaceB.MyFeature + { + OnActivation = f => order.Add(f) + }; + var feature = new NamespaceA.MyFeature + { + OnActivation = f => order.Add(f) + }; + + var settings = new SettingsHolder(); + var featureSettings = new FeatureActivator(settings); + + featureSettings.Add(dependingFeature); + featureSettings.Add(feature); + + settings.EnableFeatureByDefault(); + + featureSettings.SetupFeatures(null, null); + + Assert.True(dependingFeature.IsActive); + + Assert.IsInstanceOf(order.First(), "Upstream dependencies should be activated first"); + } + } +} + +namespace NamespaceA +{ + using NServiceBus.Core.Tests.Features; + + public class MyFeature : TestFeature + { + } +} + +namespace NamespaceB +{ + using NServiceBus.Core.Tests.Features; + + public class MyFeature : TestFeature + { + public MyFeature() + { + EnableByDefault(); + DependsOn(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Features/FeatureSettingsTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureSettingsTests.cs index 16870ed297f..a126ad58b00 100644 --- a/src/NServiceBus.Core.Tests/Features/FeatureSettingsTests.cs +++ b/src/NServiceBus.Core.Tests/Features/FeatureSettingsTests.cs @@ -3,25 +3,34 @@ using System; using System.Linq; using NServiceBus.Features; + using Transport; using NUnit.Framework; using Settings; [TestFixture] public class FeatureSettingsTests { + private FeatureActivator featureSettings; + private SettingsHolder settings; + + [SetUp] + public void Init() + { + settings = new SettingsHolder(); + settings.Set(new MsmqTransport()); + featureSettings = new FeatureActivator(settings); + } + [Test] public void Should_check_prerequisites() { var featureWithTrueCondition = new MyFeatureWithSatisfiedPrerequisite(); var featureWithFalseCondition = new MyFeatureWithUnsatisfiedPrerequisite(); - var featureSettings = new FeatureActivator(new SettingsHolder()); - featureSettings.Add(featureWithTrueCondition); featureSettings.Add(featureWithFalseCondition); - - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(featureWithTrueCondition.IsActive); Assert.False(featureWithFalseCondition.IsActive); @@ -32,26 +41,20 @@ public void Should_check_prerequisites() [Test] public void Should_register_defaults_if_feature_is_activated() { - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - featureSettings.Add(new MyFeatureWithDefaults()); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.True(settings.HasSetting("Test1")); } - [Test,Ignore("We need to discuss if this is possible since prereqs can only be checked when settings is locked. And with settings locked we can't register defaults. So there is always a chance that the feature decides to not go ahead with the setup and in that case defaults would already been applied")] + [Test,Ignore("Discuss if this is possible since pre-requirements can only be checked when settings is locked. And with settings locked we can't register defaults. So there is always a chance that the feature decides to not go ahead with the setup and in that case defaults would already been applied")] public void Should_not_register_defaults_if_feature_is_not_activated() { - var settings = new SettingsHolder(); - var featureSettings = new FeatureActivator(settings); - featureSettings.Add(new MyFeatureWithDefaultsNotActive()); featureSettings.Add(new MyFeatureWithDefaultsNotActiveDueToUnsatisfiedPrerequisite()); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); Assert.False(settings.HasSetting("Test1")); Assert.False(settings.HasSetting("Test2")); @@ -115,10 +118,7 @@ protected TestFeature() { Defaults(s => { - if (OnDefaults != null) - { - OnDefaults(this); - } + OnDefaults?.Invoke(this); }); } @@ -133,10 +133,7 @@ public bool Enabled protected internal override void Setup(FeatureConfigurationContext context) { - if (OnActivation != null) - { - OnActivation(this); - } + OnActivation?.Invoke(this); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Features/FeatureStartupTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureStartupTests.cs index b2fd5403083..3a72af1952a 100644 --- a/src/NServiceBus.Core.Tests/Features/FeatureStartupTests.cs +++ b/src/NServiceBus.Core.Tests/Features/FeatureStartupTests.cs @@ -2,7 +2,9 @@ { using System; using System.Collections.Generic; + using System.Threading.Tasks; using NServiceBus.Features; + using Transport; using NUnit.Framework; using ObjectBuilder; using Settings; @@ -10,56 +12,125 @@ [TestFixture] public class FeatureStartupTests { + private FeatureActivator featureSettings; + private SettingsHolder settings; + + [SetUp] + public void Init() + { + settings = new SettingsHolder(); + settings.Set(new MsmqTransport()); + featureSettings = new FeatureActivator(settings); + } + [Test] - public void Should_not_activate_features_with_unmet_dependencies() + public async Task Should_start_and_stop_features() { var feature = new FeatureWithStartupTask(); - - var featureSettings = new FeatureActivator(new SettingsHolder()); featureSettings.Add(feature); var builder = new FakeBuilder(typeof(FeatureWithStartupTask.Runner)); - featureSettings.SetupFeatures(new FeatureConfigurationContext(null)); + featureSettings.SetupFeatures(null, null); - featureSettings.StartFeatures(builder); - featureSettings.StopFeatures(builder); + await featureSettings.StartFeatures(builder, null); + await featureSettings.StopFeatures(null); Assert.True(FeatureWithStartupTask.Runner.Started); Assert.True(FeatureWithStartupTask.Runner.Stopped); } + [Test] + public async Task Should_dispose_feature_when_they_implement_IDisposable() + { + var feature = new FeatureWithStartupTaskWhichIsDisposable(); + + featureSettings.Add(feature); + + var builder = new FakeBuilder(typeof(FeatureWithStartupTaskWhichIsDisposable.Runner)); + + featureSettings.SetupFeatures(null, null); + + await featureSettings.StartFeatures(builder, null); + await featureSettings.StopFeatures(null); + + Assert.True(FeatureWithStartupTaskWhichIsDisposable.Runner.Disposed); + } + class FeatureWithStartupTask : TestFeature { public FeatureWithStartupTask() { EnableByDefault(); - RegisterStartupTask(); } - public class Runner:FeatureStartupTask + protected internal override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(new Runner()); + } + + public class Runner : FeatureStartupTask { - protected override void OnStart() + protected override Task OnStart(IMessageSession session) { Started = true; + return TaskEx.CompletedTask; } - protected override void OnStop() + protected override Task OnStop(IMessageSession session) { Stopped = true; + return TaskEx.CompletedTask; } - public static bool Started { get; set; } - public static bool Stopped { get; set; } + public static bool Started { get; private set; } + public static bool Stopped { get; private set; } + } + } + + class FeatureWithStartupTaskWhichIsDisposable : TestFeature + { + public FeatureWithStartupTaskWhichIsDisposable() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(new Runner()); + } + + public class Runner : FeatureStartupTask, IDisposable + { + protected override Task OnStart(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + public void Dispose() + { + Disposed = true; + } + + public static bool Disposed { get; private set; } } } } public class FakeBuilder : IBuilder { - readonly Type type; + Type type; + public FakeBuilder() + { + + } public FakeBuilder(Type type) { this.type = type; @@ -74,7 +145,7 @@ public object Build(Type typeToBuild) { if (typeToBuild != type) { - throw new Exception("Not the expected task"); + throw new Exception("Not the expected type"); } return Activator.CreateInstance(typeToBuild); } diff --git a/src/NServiceBus.Core.Tests/Features/FeatureTests.cs b/src/NServiceBus.Core.Tests/Features/FeatureTests.cs index 17720199a62..d0a71d3c01a 100644 --- a/src/NServiceBus.Core.Tests/Features/FeatureTests.cs +++ b/src/NServiceBus.Core.Tests/Features/FeatureTests.cs @@ -10,7 +10,6 @@ public void Should_be_disabled_be_default() { var feature = new MyFeature(); - Assert.False(feature.IsEnabledByDefault); } @@ -19,7 +18,6 @@ public void Should_be_allow_features_to_request_being_enabled_by_default() { var feature = new MyEnabledByDefaultFeature(); - Assert.True(feature.IsEnabledByDefault); } diff --git a/src/NServiceBus.Core.Tests/FuncBuilder.cs b/src/NServiceBus.Core.Tests/FuncBuilder.cs deleted file mode 100644 index 6f8d5b27d6f..00000000000 --- a/src/NServiceBus.Core.Tests/FuncBuilder.cs +++ /dev/null @@ -1,153 +0,0 @@ -namespace NServiceBus.Core.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using ObjectBuilder; - using ObjectBuilder.Common; - - public class FuncBuilder : IBuilder,IContainer - { - readonly IList>> funcs = new List>>(); - - public void Dispose() - { - - } - public void Register() where T : new() - { - Register(typeof(T), ()=> new T()); - } - - public void Register(Type t) - { - Register(t, () => Activator.CreateInstance(t)); - } - public void Register(Func func) - { - Register(typeof(T), func); - } - - public void Register(Type t, Func func) - { - funcs.Add(new Tuple>(t, func)); - } - - public object Build(Type typeToBuild) - { - try - { - var fn = funcs.FirstOrDefault(f => f.Item1 == typeToBuild); - - if (fn == null) - { - var @interface = typeToBuild.GetInterfaces().FirstOrDefault(); - if (@interface != null) - { - fn = funcs.FirstOrDefault(f => f.Item1 == @interface); - } - } - - object result; - - if (fn != null) - { - result = fn.Item2(); - } - else - { - result = Activator.CreateInstance(typeToBuild); - } - - //enable property injection - var propertyInfos = result.GetType().GetProperties().Where(pi => pi.PropertyType != result.GetType()); - var propsWithoutFuncs = propertyInfos - .Select(p => p.PropertyType) - .Intersect(funcs.Select(f => f.Item1)).ToList(); - - propsWithoutFuncs.ForEach(propertyTypeToSet => propertyInfos.First(p => p.PropertyType == propertyTypeToSet) - .SetValue(result, Build(propertyTypeToSet), null)); - - return result; - - } - catch (Exception ex) - { - throw new Exception("Failed to build type: " + typeToBuild,ex); - } - } - - public IContainer BuildChildContainer() - { - throw new NotImplementedException(); - } - - public IBuilder CreateChildBuilder() - { - return this; - } - - public T Build() - { - try - { - return (T) Build(typeof(T)); - } - catch (Exception exception) - { - throw new ApplicationException(string.Format("Could not build {0}", typeof(T)), exception); - } - } - - public IEnumerable BuildAll() - { - return funcs.Where(f => f.Item1 == typeof(T)) - .Select(f => (T)f.Item2()) - .ToList(); - } - - public IEnumerable BuildAll(Type typeToBuild) - { - return funcs.Where(f => f.Item1 == typeToBuild) - .Select(f => f.Item2()) - .ToList(); - } - - public void Configure(Type component, DependencyLifecycle dependencyLifecycle) - { - Register(component); - } - - public void Configure(Func component, DependencyLifecycle dependencyLifecycle) - { - throw new NotImplementedException(); - } - - public void ConfigureProperty(Type component, string property, object value) - { - - } - - public void RegisterSingleton(Type lookupType, object instance) - { - Register(lookupType,()=>instance); - } - - public bool HasComponent(Type componentType) - { - throw new NotImplementedException(); - } - - public void Release(object instance) - { - - } - - public void BuildAndDispatch(Type typeToBuild, Action action) - { - var obj = Build(typeToBuild); - - action(obj); - } - } -} diff --git a/src/NServiceBus.Core.Tests/GitFlowVersionTests.cs b/src/NServiceBus.Core.Tests/GitFlowVersionTests.cs index 221cf1eba13..8015fe3fcc8 100644 --- a/src/NServiceBus.Core.Tests/GitFlowVersionTests.cs +++ b/src/NServiceBus.Core.Tests/GitFlowVersionTests.cs @@ -9,9 +9,9 @@ public class GitFlowVersionTests [Test] public void Verify_has_a_version_and_can_be_parsed() { - Assert.IsNotNullOrEmpty(GitFlowVersion.MajorMinor); + Assert.That(GitFlowVersion.MajorMinor, Is.Not.Null.Or.Empty); Version.Parse(GitFlowVersion.MajorMinor); - Assert.IsNotNullOrEmpty(GitFlowVersion.MajorMinorPatch); + Assert.That(GitFlowVersion.MajorMinorPatch, Is.Not.Null.Or.Empty); Version.Parse(GitFlowVersion.MajorMinorPatch); } } diff --git a/src/NServiceBus.Core.Tests/GlobalTestSetup.cs b/src/NServiceBus.Core.Tests/GlobalTestSetup.cs new file mode 100644 index 00000000000..b1d5df6529c --- /dev/null +++ b/src/NServiceBus.Core.Tests/GlobalTestSetup.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Core.Tests +{ + using System.Globalization; + using System.Threading; + using NUnit.Framework; + + [SetUpFixture] + public class GlobalTestSetup + { + [OneTimeSetUp] + public void Initialize() + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Handlers/MessageHandlerRegistryTests.cs b/src/NServiceBus.Core.Tests/Handlers/MessageHandlerRegistryTests.cs new file mode 100644 index 00000000000..30d7688b016 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Handlers/MessageHandlerRegistryTests.cs @@ -0,0 +1,114 @@ +namespace NServiceBus.Core.Tests.Handlers +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + using Unicast; + + public class MessageHandlerRegistryTests + { + [TestCase(typeof(HandlerWithIMessageSessionProperty))] + [TestCase(typeof(HandlerWithIEndpointInstanceProperty))] + [TestCase(typeof(HandlerWithIMessageSessionCtorDep))] + [TestCase(typeof(HandlerWithIEndpointInstanceCtorDep))] + [TestCase(typeof(HandlerWithInheritedIMessageSessionPropertyDep))] + [TestCase(typeof(SagaWithIllegalDep))] + public void ShouldThrowIfUserTriesToBypassTheHandlerContext(Type handlerType) + { + var registry = new MessageHandlerRegistry(new Conventions()); + + Assert.Throws(() => registry.RegisterHandler(handlerType)); + } + + class HandlerWithIMessageSessionProperty : IHandleMessages + { + public IMessageSession MessageSession { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + } + + class HandlerWithIEndpointInstanceProperty : IHandleMessages + { + public IEndpointInstance EndpointInstance { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + } + + class HandlerWithIMessageSessionCtorDep : IHandleMessages + { + public HandlerWithIMessageSessionCtorDep(IMessageSession messageSession) + { + MessageSession = messageSession; + } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + IMessageSession MessageSession; + } + + class HandlerWithInheritedIMessageSessionPropertyDep : HandlerBaseWithIMessageSessionDep, IHandleMessages + { + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + } + + class HandlerWithIEndpointInstanceCtorDep : IHandleMessages + { + public HandlerWithIEndpointInstanceCtorDep(IEndpointInstance endpointInstance) + { + this.endpointInstance = endpointInstance; + } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + IEndpointInstance endpointInstance; + } + + class SagaWithIllegalDep : Saga, IAmStartedByMessages + { + public SagaWithIllegalDep(IEndpointInstance endpointInstance) + { + this.endpointInstance = endpointInstance; + } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + throw new NotImplementedException(); + } + + IEndpointInstance endpointInstance; + + public class MySagaData : ContainSagaData + { + } + } + + class HandlerBaseWithIMessageSessionDep + { + public IMessageSession MessageSession { get; set; } + } + + class MyMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Hosting/HostInfoSettingsTests.cs b/src/NServiceBus.Core.Tests/Hosting/HostInfoSettingsTests.cs new file mode 100644 index 00000000000..0f4d69b7aa0 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Hosting/HostInfoSettingsTests.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.Hosting.Tests +{ + using System; + using Features; + using NUnit.Framework; + + [TestFixture] + public class HostInfoSettingsTests + { + [Test] + public void It_overrides_the_host_id() + { + var requestedId = Guid.NewGuid(); + var busConfig = new EndpointConfiguration("myendpoint"); + + busConfig.UniquelyIdentifyRunningInstance().UsingCustomIdentifier(requestedId); + + var configuredId = busConfig.Settings.Get(HostInformationFeature.HostIdSettingsKey); + Assert.AreEqual(requestedId, configuredId); + } + + [Test] + public void It_allows_to_generate_a_deterministic_id_using_instance_and_host_names() + { + var busConfig = new EndpointConfiguration("myendpoint"); + + Assert.IsFalse(busConfig.Settings.HasSetting(HostInformationFeature.HostIdSettingsKey)); + + busConfig.UniquelyIdentifyRunningInstance().UsingNames("Instance","Host"); + + Assert.IsTrue(busConfig.Settings.HasExplicitValue(HostInformationFeature.HostIdSettingsKey)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Config/PathUtilities_Tests.cs b/src/NServiceBus.Core.Tests/Hosting/PathUtilities_Tests.cs similarity index 78% rename from src/NServiceBus.Core.Tests/Unicast/Config/PathUtilities_Tests.cs rename to src/NServiceBus.Core.Tests/Hosting/PathUtilities_Tests.cs index 069b88099a4..4c0a77edbb0 100644 --- a/src/NServiceBus.Core.Tests/Unicast/Config/PathUtilities_Tests.cs +++ b/src/NServiceBus.Core.Tests/Hosting/PathUtilities_Tests.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Features.Tests +namespace NServiceBus.Hosting.Tests { using NUnit.Framework; @@ -8,24 +8,24 @@ public class PathUtilities_Tests [Test] public void Parse_from_path_without_spaces_but_with_quotes() { - var path = PathUtilities.SanitizedPath("\"pathto\\mysuperduper.exe\" somevar"); + var path = PathUtilities.SanitizedPath("\"pathto\\my.exe\" somevar"); - Assert.AreEqual("pathto\\mysuperduper.exe", path); + Assert.AreEqual("pathto\\my.exe", path); } [Test] public void Parse_from_path_without_spaces_but_without_quotes() { - var path = PathUtilities.SanitizedPath("pathto\\mysuperduper.exe somevar"); + var path = PathUtilities.SanitizedPath("pathto\\my.exe somevar"); - Assert.AreEqual("pathto\\mysuperduper.exe", path); + Assert.AreEqual("pathto\\my.exe", path); } [Test] public void Paths_without_spaces_are_equal() { - var path1 = PathUtilities.SanitizedPath("\"pathto\\mysuperduper.exe\" somevar"); - var path2 = PathUtilities.SanitizedPath("pathto\\mysuperduper.exe somevar"); + var path1 = PathUtilities.SanitizedPath("\"pathto\\my.exe\" somevar"); + var path2 = PathUtilities.SanitizedPath("pathto\\my.exe somevar"); Assert.AreEqual(path1, path2); } diff --git a/src/NServiceBus.Core.Tests/Installers/PerformanceMonitorUsersInstallerTests.cs b/src/NServiceBus.Core.Tests/Installers/PerformanceMonitorUsersInstallerTests.cs deleted file mode 100644 index 0c0450a4c5f..00000000000 --- a/src/NServiceBus.Core.Tests/Installers/PerformanceMonitorUsersInstallerTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus.Core.Tests.Installers -{ - using NUnit.Framework; - - [TestFixture] - public class PerformanceMonitorUsersInstallerTests - { - [Test] - [Explicit] - public void Integration() - { - var installer = new PerformanceMonitorUsersInstaller(); - installer.Install(@"location\username", null); - } - } -} diff --git a/src/NServiceBus.Core.Tests/Lib/TestAssembly.dll b/src/NServiceBus.Core.Tests/Lib/TestAssembly.dll deleted file mode 100644 index 86ada66f8cd..00000000000 Binary files a/src/NServiceBus.Core.Tests/Lib/TestAssembly.dll and /dev/null differ diff --git a/src/NServiceBus.Core.Tests/Licensing/LicenseExpiredFormDisplayerTests.cs b/src/NServiceBus.Core.Tests/Licensing/LicenseExpiredFormDisplayerTests.cs deleted file mode 100644 index 9b55f4ff7b5..00000000000 --- a/src/NServiceBus.Core.Tests/Licensing/LicenseExpiredFormDisplayerTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Core.Tests.Licensing -{ - using System; - using NServiceBus.Licensing; - using NUnit.Framework; - - [TestFixture] - public class LicenseExpiredFormDisplayerTests - { - - [Test] - [Explicit] - public void ShowForm14Days() - { - LicenseExpiredFormDisplayer.PromptUserForLicense(Particular.Licensing.License.TrialLicense(DateTime.Today.AddDays(33))); - } - - - [Test] - [Explicit] - public void ShowForm45Days() - { - LicenseExpiredFormDisplayer.PromptUserForLicense(null); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Logging/RollingLoggerTests.cs b/src/NServiceBus.Core.Tests/Logging/RollingLoggerTests.cs index 1a6452d03f4..cdfbc955797 100644 --- a/src/NServiceBus.Core.Tests/Logging/RollingLoggerTests.cs +++ b/src/NServiceBus.Core.Tests/Logging/RollingLoggerTests.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; - using NServiceBus.Licensing; - using NServiceBus.Logging; using NUnit.Framework; [TestFixture] @@ -217,40 +215,6 @@ public void When_max_file_size_is_exceeded_sequence_number_is_added() } } - [Test] - [Explicit] - public void MaxFilesTest() - { - using (var tempPath = new TempPath()) - { - var logger = new RollingLogger(tempPath.TempDirectory, maxFileSize: 10) - { - GetDate = () => new DateTime(2010, 10, 1) - }; - for (var i = 0; i < 1000000000; i++) - { - logger.Write("Some long text"); - } - } - } - - [Test] - [Explicit] - public void ManyWritesTest() - { - using (var tempPath = new TempPath()) - { - var logger = new RollingLogger(tempPath.TempDirectory) - { - GetDate = () => new DateTime(2010, 10, 1) - }; - for (var i = 0; i < 1000000; i++) - { - logger.Write("Some long text"); - } - } - } - [Test] public void When_many_sequence_files_are_written_the_max_is_not_exceeded() { diff --git a/src/NServiceBus.Core.Tests/MessageConventionSpecs.cs b/src/NServiceBus.Core.Tests/MessageConventionSpecs.cs new file mode 100644 index 00000000000..f56dcf61b2d --- /dev/null +++ b/src/NServiceBus.Core.Tests/MessageConventionSpecs.cs @@ -0,0 +1,81 @@ +namespace NServiceBus.Core.Tests +{ + using NUnit.Framework; + + [TestFixture] + public class When_applying_message_conventions_to_messages : MessageConventionTestBase + { + [Test] + public void Should_cache_the_message_convention() + { + var timesCalled = 0; + conventions = new Conventions + { + IsMessageTypeAction = t => + { + timesCalled++; + return false; + } + }; + + conventions.IsMessageType(GetType()); + Assert.AreEqual(1, timesCalled); + + conventions.IsMessageType(GetType()); + Assert.AreEqual(1, timesCalled); + } + } + + [TestFixture] + public class When_applying_message_conventions_to_events : MessageConventionTestBase + { + [Test] + public void Should_cache_the_message_convention() + { + var timesCalled = 0; + conventions = new Conventions + { + IsEventTypeAction = t => + { + timesCalled++; + return false; + } + }; + + conventions.IsEventType(GetType()); + Assert.AreEqual(1, timesCalled); + + conventions.IsEventType(GetType()); + Assert.AreEqual(1, timesCalled); + } + } + + [TestFixture] + public class When_applying_message_conventions_to_commands : MessageConventionTestBase + { + [Test] + public void Should_cache_the_message_convention() + { + var timesCalled = 0; + conventions = new Conventions + { + IsCommandTypeAction = t => + { + timesCalled++; + return false; + } + }; + + conventions.IsCommandType(GetType()); + Assert.AreEqual(1, timesCalled); + + conventions.IsCommandType(GetType()); + Assert.AreEqual(1, timesCalled); + } + } + + public class MessageConventionTestBase + { + protected Conventions conventions; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/MessageMapper/MessageMapperTests.cs b/src/NServiceBus.Core.Tests/MessageMapper/MessageMapperTests.cs new file mode 100644 index 00000000000..d5a330ec16c --- /dev/null +++ b/src/NServiceBus.Core.Tests/MessageMapper/MessageMapperTests.cs @@ -0,0 +1,132 @@ +namespace MessageMapperTests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Threading.Tasks; + using NServiceBus.MessageInterfaces.MessageMapper.Reflection; + using NUnit.Framework; + + [TestFixture] + public class MessageMapperTests + { + [Test] + public void Initialize_ShouldBeThreadsafe() + { + var mapper = new MessageMapper(); + + Parallel.For(0, 10, i => + { + mapper.Initialize(new[] + { + typeof(SampleMessageClass), + typeof(ISampleMessageInterface), + typeof(ClassImplementingIEnumerable<>) + }); + }); + } + + [Test] + public void CreateInstance_WhenMessageInitialized_ShouldBeThreadsafe() + { + var mapper = new MessageMapper(); + + mapper.Initialize(new[] + { + typeof(SampleMessageClass), + typeof(ISampleMessageInterface), + typeof(ClassImplementingIEnumerable<>) + }); + + Parallel.For(0, 10, i => + { + mapper.CreateInstance(); + mapper.CreateInstance(); + mapper.CreateInstance>(); + }); + } + + [Test] + public void CreateInstance_WhenMessageNotInitialized_ShouldBeThreadsafe() + { + var mapper = new MessageMapper(); + + Parallel.For(0, 10, i => + { + mapper.CreateInstance(); + mapper.CreateInstance(); + mapper.CreateInstance>(); + }); + } + + [Test] + public void ShouldAllowMultipleMapperInstancesPerAppDomain() + { + Parallel.For(0, 10, i => + { + var mapper = new MessageMapper(); + mapper.CreateInstance(); + mapper.CreateInstance(); + mapper.CreateInstance>(); + }); + } + + [Test] + public void Should_create_instance_of_concrete_type_with_illegal_interface_property() + { + var mapper = new MessageMapper(); + + mapper.Initialize(new[] { typeof(ConcreteMessageWithIllegalInterfaceProperty) }); + + mapper.CreateInstance(); + } + + [Test] + public void Should_fail_for_interface_message_with_illegal_interface_property() + { + var mapper = new MessageMapper(); + + Assert.Throws(() => mapper.Initialize(new[] { typeof(InterfaceMessageWithIllegalInterfaceProperty) })); + } + + + public class SampleMessageClass + { + } + + public interface ISampleMessageInterface + { + } + + public class ClassImplementingIEnumerable : IEnumerable + { + public IEnumerator GetEnumerator() + { + return new List.Enumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public class ConcreteMessageWithIllegalInterfaceProperty + { + public IIllegalProperty MyProperty { get; set; } + } + + public interface InterfaceMessageWithIllegalInterfaceProperty + { + IIllegalProperty MyProperty { get; set; } + } + + public interface IIllegalProperty + { + string SomeProperty { get; set; } + + //this is not supported by our mapper + void SomeMethod(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_interfaces.cs b/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_interfaces.cs index 5ba1e33192c..316657b7e42 100644 --- a/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_interfaces.cs +++ b/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_interfaces.cs @@ -38,12 +38,13 @@ public interface Interface : IMessage } [Test] - public void Interfaces_with_methods_should_be_ignored() + public void Interfaces_with_methods_are_not_supported() { var mapper = new MessageMapper(); - mapper.Initialize(new[] { typeof(InterfaceWithMethods) }); - - Assert.Null(mapper.GetMappedTypeFor(typeof(InterfaceWithMethods))); + Assert.Throws(() => mapper.Initialize(new[] + { + typeof(InterfaceWithMethods) + })); } public interface InterfaceWithMethods @@ -181,45 +182,14 @@ public CustomAttributeWithValueProperties(string name, bool flag, int age) public int MyAge { get; set; } } - private bool PropertyContainsAttribute(string propertyName, Type attributeType, object obj) + bool PropertyContainsAttribute(string propertyName, Type attributeType, object obj) { return obj.GetType().GetProperty(propertyName).GetCustomAttributes(attributeType,true).Length > 0; } + } - [Test] - public void Should_map_when_deriving_from_another_interface_with_the_same_property_name_but_different_type() - { - var mapper = new MessageMapper(); - var genericInterfaceType = typeof(InterfaceWithGenericProperty); - mapper.Initialize(new[] { genericInterfaceType }); - Assert.NotNull(mapper.GetMappedTypeFor(genericInterfaceType)); - } - - public interface InterfaceWithGenericProperty - { - object Original { get; set; } - } - - public interface InterfaceWithGenericProperty : InterfaceWithGenericProperty - { - new T Original { get; set; } - } - public interface IBar - { - string Yeah { get; set; } - } - public class FancyMessage : InterfaceWithGenericProperty - { - public IBar Original { get; set; } - object InterfaceWithGenericProperty.Original - { - get { return Original; } - set { Original = (IBar)value; } - } - } - } - + } diff --git a/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_usinggenerics.cs b/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_usinggenerics.cs index e0a5fd98211..851cd38ff9a 100644 --- a/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_usinggenerics.cs +++ b/src/NServiceBus.Core.Tests/MessageMapper/When_mapping_usinggenerics.cs @@ -75,7 +75,7 @@ public abstract class SimpleAbstractClass : IMessage string SomeProperty { get; set; } } - [Test,Ignore] + [Test,Ignore("Does not work.")] public void Class_abstract_with_methods_should_not_be_mapped() { var mapper = new MessageMapper(); @@ -118,8 +118,6 @@ public interface InterfaceGenericWithMethods : IMessage where T : Data void MethodOnInterface(T myMessage); } - - [Test] public void Interfaces_with_only_properties_should_be_mapped() { @@ -133,6 +131,30 @@ public interface InterfaceWithOnlyProperties : IMessage { string SomeProperty { get; set; } } + + [Test] + public void Interfaces_with_inheritance_and_property_overload_should_be_mapped() + { + var mapper = new MessageMapper(); + var genericInterfaceType = typeof(InterfaceWithGenericProperty); + mapper.Initialize(new[] { genericInterfaceType }); + Assert.NotNull(mapper.GetMappedTypeFor(genericInterfaceType)); + } + + public interface InterfaceWithGenericProperty + { + object Original { get; set; } + } + + public interface InterfaceWithGenericProperty : InterfaceWithGenericProperty + { + new T Original { get; set; } + } + + public interface ISpecific + { + string Value { get; set; } + } } public abstract class Data diff --git a/src/NServiceBus.Core.Tests/Msmq/ConnectionStringParserTests.cs b/src/NServiceBus.Core.Tests/Msmq/ConnectionStringParserTests.cs index 251c25eb750..02228530398 100644 --- a/src/NServiceBus.Core.Tests/Msmq/ConnectionStringParserTests.cs +++ b/src/NServiceBus.Core.Tests/Msmq/ConnectionStringParserTests.cs @@ -1,7 +1,6 @@ namespace NServiceBus.Core.Tests.Msmq { using System; - using NServiceBus.Config; using NUnit.Framework; [TestFixture] diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqAddressTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqAddressTests.cs new file mode 100644 index 00000000000..0bea0b7a7fb --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqAddressTests.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class MsmqAddressTests + { + + [Test] + public void If_both_addresses_are_specified_via_host_name_it_should_not_convert() + { + var address = new MsmqAddress("replyToAddress", "replyToMachine"); + var returnAddress = address.MakeCompatibleWith(new MsmqAddress("someQueue","destinationmachine"), _ => + { + throw new Exception("Should not call the lookup method"); + }); + Assert.AreEqual("replyToMachine", returnAddress.Machine); + } + + [Test] + public void If_both_addresses_are_specified_via_ip_it_should_not_convert() + { + var address = new MsmqAddress("replyToAddress", "202.171.13.141"); + var returnAddress = address.MakeCompatibleWith(new MsmqAddress("someQueue", "202.171.13.140"), _ => + { + throw new Exception("Should not call the lookup method"); + }); + Assert.AreEqual("202.171.13.141", returnAddress.Machine); + } + + [Test] + public void If_reference_address_is_specified_via_ip_and_this_is_specified_via_host_name_it_should_convert_to_ip() + { + var address = new MsmqAddress("replyToAddress", "replyToMachine"); + var returnAddress = address.MakeCompatibleWith(new MsmqAddress("someQueue", "202.171.13.140"), _ => "10.10.10.10"); + Assert.AreEqual("10.10.10.10", returnAddress.Machine); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqExtensionsTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqExtensionsTests.cs new file mode 100644 index 00000000000..ee254f6d447 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqExtensionsTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System.Messaging; + using System.Security.Principal; + using NUnit.Framework; + using Support; + + [TestFixture] + public class MsmqExtensionsTests + { + static string LocalEveryoneGroupName = new SecurityIdentifier(WellKnownSidType.WorldSid, null).Translate(typeof(NTAccount)).ToString(); + static string LocalAnonymousLogonName = new SecurityIdentifier(WellKnownSidType.AnonymousSid, null).Translate(typeof(NTAccount)).ToString(); + + string path; + MessageQueue queue; + + [TestFixtureSetUp] + public void Setup() + { + var queueName = "permissionsTest"; + path = $@"{RuntimeEnvironment.MachineName}\private$\{queueName}"; + MsmqHelpers.DeleteQueue(path); + MsmqHelpers.CreateQueue(path); + + queue = new MessageQueue(path); + } + + [TestFixtureTearDown] + public void Teardown() + { + queue.Dispose(); + MsmqHelpers.DeleteQueue(path); + } + + [Test] + public void GetPermissions_returns_queue_access_rights() + { + queue.SetPermissions(LocalEveryoneGroupName, MessageQueueAccessRights.WriteMessage | MessageQueueAccessRights.ReceiveMessage, AccessControlEntryType.Allow); + MessageQueueAccessRights? rights; + if (!queue.TryGetPermissions(LocalEveryoneGroupName, out rights)) + { + Assert.Fail("Unable to read permissions off a queue"); + } + + Assert.IsTrue(rights.HasValue); + Assert.True(rights.Value.HasFlag(MessageQueueAccessRights.WriteMessage)); + Assert.True(rights.Value.HasFlag(MessageQueueAccessRights.ReceiveMessage)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqFailureInfoStorageTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqFailureInfoStorageTests.cs new file mode 100644 index 00000000000..bea744d27e5 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqFailureInfoStorageTests.cs @@ -0,0 +1,126 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System; + using System.Collections.Generic; + using NUnit.Framework; + + public class MsmqFailureInfoStorageTests + { + [Test] + public void When_recording_failure_initially_should_store_one_failed_attempt_and_exception() + { + var messageId = Guid.NewGuid().ToString("D"); + var exception = new Exception(); + + var storage = GetFailureInfoStorage(); + + storage.RecordFailureInfoForMessage(messageId, exception); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + storage.TryGetFailureInfoForMessage(messageId, out failureInfo); + + Assert.NotNull(failureInfo); + Assert.AreEqual(1, failureInfo.NumberOfProcessingAttempts); + Assert.AreSame(exception, failureInfo.Exception); + } + + [Test] + public void When_recording_failure_many_times_should_store_number_of_attempts_and_last_exception() + { + var messageId = Guid.NewGuid().ToString("D"); + var secondException = new Exception(); + + var storage = GetFailureInfoStorage(); + + storage.RecordFailureInfoForMessage(messageId, new Exception()); + storage.RecordFailureInfoForMessage(messageId, secondException); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + storage.TryGetFailureInfoForMessage(messageId, out failureInfo); + + Assert.NotNull(failureInfo); + Assert.AreEqual(2, failureInfo.NumberOfProcessingAttempts); + Assert.AreSame(secondException, failureInfo.Exception); + } + + [Test] + public void When_clearing_failure_should_return_null_on_subsequent_retrieval() + { + var messageId = Guid.NewGuid().ToString("D"); + + var storage = GetFailureInfoStorage(); + + storage.RecordFailureInfoForMessage(messageId, new Exception()); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + + storage.TryGetFailureInfoForMessage(messageId, out failureInfo); + Assert.NotNull(failureInfo); + + storage.ClearFailureInfoForMessage(messageId); + + storage.TryGetFailureInfoForMessage(messageId, out failureInfo); + Assert.IsNull(failureInfo); + } + + [Test] + public void When_recording_more_than_max_number_of_failures_should_remove_least_recently_used_entry() + { + const int MaxElements = 50; + var storage = new MsmqFailureInfoStorage(maxElements: MaxElements); + + var lruMessageId = Guid.NewGuid().ToString("D"); + + storage.RecordFailureInfoForMessage(lruMessageId, new Exception()); + + for (var i = 0; i < MaxElements; ++i) + { + var messageId = Guid.NewGuid().ToString("D"); + var exception = new Exception(); + + storage.RecordFailureInfoForMessage(messageId, exception); + } + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + storage.TryGetFailureInfoForMessage(lruMessageId, out failureInfo); + + Assert.IsNull(failureInfo); + } + + [Test] + public void When_recording_a_failure_for_a_message_it_should_not_be_treated_as_least_recently_used() + { + const int MaxElements = 50; + var storage = new MsmqFailureInfoStorage(MaxElements); + + var lruMessageId = Guid.NewGuid().ToString("D"); + + storage.RecordFailureInfoForMessage(lruMessageId, new Exception()); + + var messageIds = new List(MaxElements); + for (var i = 0; i < MaxElements; ++i) + { + messageIds.Add(Guid.NewGuid().ToString("D")); + } + + for (var i = 0; i < MaxElements - 1; ++i) + { + storage.RecordFailureInfoForMessage(messageIds[i], new Exception()); + } + + storage.RecordFailureInfoForMessage(lruMessageId, new Exception()); + + storage.RecordFailureInfoForMessage(messageIds[MaxElements - 1], new Exception()); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + storage.TryGetFailureInfoForMessage(lruMessageId, out failureInfo); + + Assert.IsNotNull(failureInfo); + } + + static MsmqFailureInfoStorage GetFailureInfoStorage() + { + return new MsmqFailureInfoStorage(10); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqHelpers.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqHelpers.cs new file mode 100644 index 00000000000..ad3fcad2038 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqHelpers.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System.Messaging; + + static class MsmqHelpers + { + public static void DeleteQueue(string path) + { + if (!MessageQueue.Exists(path)) + { + return; + } + MessageQueue.Delete(path); + } + + public static void CreateQueue(string path) + { + if (MessageQueue.Exists(path)) + { + return; + } + using (MessageQueue.Create(path, true)) + { + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqMessageDispatcherTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqMessageDispatcherTests.cs new file mode 100644 index 00000000000..e001f75d32f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqMessageDispatcherTests.cs @@ -0,0 +1,81 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using Extensibility; + using NServiceBus.Routing; + using Transport; + using NUnit.Framework; + using Support; + + [TestFixture] + public class MsmqMessageDispatcherTests + { + [Test] + public void Should_set_label_when_convention_configured() + { + var queueName = "labelTest"; + var path = $@"{RuntimeEnvironment.MachineName}\private$\{queueName}"; + try + { + MsmqHelpers.DeleteQueue(path); + MsmqHelpers.CreateQueue(path); + var messageSender = new MsmqMessageDispatcher(new MsmqSettings(), _ => "mylabel"); + + var bytes = new byte[] + { + 1 + }; + var headers = new Dictionary(); + var outgoingMessage = new OutgoingMessage("1", headers, bytes); + var transportOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(queueName), DispatchConsistency.Default); + messageSender.Dispatch(new TransportOperations(transportOperation), new TransportTransaction(), new ContextBag()); + var messageLabel = ReadMessageLabel(path); + Assert.AreEqual("mylabel", messageLabel); + + } + finally + { + MsmqHelpers.DeleteQueue(path); + } + } + [Test] + public void Should_use_string_empty_label_when_no_convention_configured() + { + var queueName = "emptyLabelTest"; + var path = $@".\private$\{queueName}"; + try + { + MsmqHelpers.DeleteQueue(path); + MsmqHelpers.CreateQueue(path); + var messageSender = new MsmqMessageDispatcher(new MsmqSettings(), pairs => string.Empty); + + var bytes = new byte[] + { + 1 + }; + var headers = new Dictionary(); + var outgoingMessage = new OutgoingMessage("1", headers, bytes); + var transportOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(queueName), DispatchConsistency.Default); + messageSender.Dispatch(new TransportOperations(transportOperation), new TransportTransaction(), new ContextBag()); + var messageLabel = ReadMessageLabel(path); + Assert.IsEmpty(messageLabel); + + } + finally + { + MsmqHelpers.DeleteQueue(path); + } + } + + static string ReadMessageLabel(string path) + { + using (var queue = new MessageQueue(path)) + using (var message = queue.Receive(TimeSpan.FromSeconds(5))) + { + return message?.Label; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqMessagePumpTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqMessagePumpTests.cs new file mode 100644 index 00000000000..0ccbc92f885 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqMessagePumpTests.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System; + using Transport; + using NUnit.Framework; + + [TestFixture] + public class MsmqMessagePumpTests + { + + [Test] + public void ShouldThrowIfConfiguredToReceiveFromRemoteQueue() + { + var messagePump = new MessagePump(mode => null); + var pushSettings = new PushSettings("queue@remote", "error", false, TransportTransactionMode.None); + + var exception = Assert.Throws(() => + { + messagePump.Init(context => null, context => null, null, pushSettings); + }); + + Assert.That(exception.Message, Does.Contain($"MSMQ Dequeuing can only run against the local machine. Invalid inputQueue name '{pushSettings.InputQueue}'.")); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqSubscriptionStorageQueueTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqSubscriptionStorageQueueTests.cs new file mode 100644 index 00000000000..ba111bd3cc5 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqSubscriptionStorageQueueTests.cs @@ -0,0 +1,308 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + using NUnit.Framework; + using MessageType = Unicast.Subscriptions.MessageType; + + [TestFixture] + public class MsmqSubscriptionStorageQueueTests + { + [Test] + public async Task Subscribe_is_persistent() + { + var queue = new FakeStorageQueue(); + var messageType = new MessageType(typeof(SomeMessage)); + var storage = CreateAndInit(queue); + + await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub2", "endpointA"), messageType, new ContextBag()); + + var storedMessages = queue.GetAllMessages().ToArray(); + Assert.That(storedMessages.Length, Is.EqualTo(2)); + + storage = CreateAndInit(queue); + var subscribers = (await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag())).ToArray(); + Assert.That(subscribers, Has.Exactly(1).Matches(s => s.TransportAddress == "sub1" && s.Endpoint == null)); + Assert.That(subscribers, Has.Exactly(1).Matches(s => s.TransportAddress == "sub2" && s.Endpoint == "endpointA")); + } + + [Test] + public async Task Unsubscribe_is_persistent() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); + storage = CreateAndInit(queue); + + await storage.Unsubscribe(new Subscriber("sub1", "endpointA"), messageType, new ContextBag()); + Assert.That(queue.GetAllMessages(), Is.Empty); + + storage = CreateAndInit(queue); + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); + Assert.AreEqual(0, subscribers.Count()); + } + + [Test] + public async Task Remove_outdated_subscriptions_on_initialization() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + await storage.Subscribe(new Subscriber("sub1", "1"), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", "2"), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", "3"), messageType, new ContextBag()); + + storage = CreateAndInit(queue); + var subscribers = (await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag())).ToArray(); + + Assert.That(subscribers.Length, Is.EqualTo(1)); + Assert.That(subscribers[0].TransportAddress, Is.EqualTo("sub1")); + Assert.That(subscribers[0].Endpoint, Is.EqualTo("3")); + Assert.That(queue.GetAllMessages().Count(), Is.EqualTo(1)); + } + + [Test] + public async Task Subscribers_are_deduplicated_based_on_transport_address_comparison_case_invariant() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("SUB1", null), messageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); + Assert.AreEqual(1, subscribers.Count()); + } + + [Test] + public async Task Can_have_multiple_subscribers_to_same_event_type() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub2", null), messageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); + Assert.AreEqual(2, subscribers.Count()); + } + + [Test] + public async Task Can_handle_legacy_and_new_format() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + var messageTypes = new[] { messageType }; + await storage.Subscribe(new Subscriber("legacy", null), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("new", "endpoint"), messageType, new ContextBag()); + + var subscribers = (await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag())).ToArray(); + + Assert.AreEqual(2, subscribers.Length); + Assert.IsTrue(subscribers.Any(s => s.TransportAddress == "legacy" && s.Endpoint == null)); + Assert.IsTrue(subscribers.Any(s => s.TransportAddress == "new" && s.Endpoint == "endpoint")); + } + + [Test] + public async Task Can_subscribe_to_multiple_events() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var someMessageType = new MessageType(typeof(SomeMessage)); + var otherMessageType = new MessageType(typeof(OtherMessage)); + await storage.Subscribe(new Subscriber("sub1", null), someMessageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", null), otherMessageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { someMessageType }, new ContextBag()); + Assert.AreEqual(1, subscribers.Count()); + + subscribers = await storage.GetSubscriberAddressesForMessage(new[] { otherMessageType }, new ContextBag()); + Assert.AreEqual(1, subscribers.Count()); + } + + [Test] + public async Task Same_subscriber_for_multiple_message_types_is_returned_only_once() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var someMessageType = new MessageType(typeof(SomeMessage)); + var otherMessageType = new MessageType(typeof(OtherMessage)); + await storage.Subscribe(new Subscriber("sub1", null), someMessageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", null), otherMessageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] + { + someMessageType, + otherMessageType + }, new ContextBag()); + + Assert.AreEqual(1, subscribers.Count()); + } + + [Test] + public async Task Two_subscribers_with_same_address_but_different_endpoint_are_considered_duplicates() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + var messageTypes = new[] { messageType }; + await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", "endpoint"), messageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag()); + + var subscriber = subscribers.Single(); + Assert.AreEqual("sub1", subscriber.TransportAddress); + Assert.AreEqual("endpoint", subscriber.Endpoint); + } + + [Test] + public async Task Unsubscribing_removes_all_subscriptions_with_same_address_but_different_endpoint_names() + { + var queue = new FakeStorageQueue(); + var storage = CreateAndInit(queue); + + var messageType = new MessageType(typeof(SomeMessage)); + var messageTypes = new[] { messageType }; + await storage.Subscribe(new Subscriber("sub1", "e1"), messageType, new ContextBag()); + await storage.Subscribe(new Subscriber("sub1", "e2"), messageType, new ContextBag()); + + await storage.Unsubscribe(new Subscriber("sub1", "e3"), messageType, new ContextBag()); + + var subscribers = await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag()); + Assert.AreEqual(0, subscribers.Count()); + } + + [Test] + public void Messages_with_the_same_timestamp_have_repeatedly_same_order() + { + var now = DateTime.Now; + + var msg1 = new MsmqSubscriptionMessage + { + ArrivedTime = now, + Id = Guid.NewGuid().ToString(), + Body = "SomeMessageType, Version=1.0.0", + Label = "address|endpoint" + }; + var msg2 = new MsmqSubscriptionMessage + { + ArrivedTime = now, + Id = Guid.NewGuid().ToString(), + Body = "SomeMessageType, Version=1.0.0", + Label = "address|endpoint" + }; + + var queue1 = new FakeStorageQueue(); + var storage1 = new MsmqSubscriptionStorage(queue1); + queue1.Messages.AddRange(new [] + { + msg1, + msg2, + }); + + var queue2 = new FakeStorageQueue(); + var storage2 = new MsmqSubscriptionStorage(queue2); + queue2.Messages.AddRange(new[] + { + msg2, // inverted order + msg1, + }); + + storage1.Init(); + storage2.Init(); + + // both endpoints should delete the same message although they have the same timestamp and are read in different order from the queue. + Assert.That(queue1.Messages.Count, Is.EqualTo(1)); + Assert.AreEqual(queue1.Messages.Single(), queue2.Messages.Single()); + } + + [Test] + public async Task Should_ignore_message_version_on_subscriptions() + { + var subscriptionMessage = new MsmqSubscriptionMessage + { + ArrivedTime = DateTime.UtcNow, + Id = Guid.NewGuid().ToString(), + Body = "SomeMessage, Version=1.0.0", + Label = "subscriberA@server1|subscriberA" + }; + + var storageQueue = new FakeStorageQueue(); + var subscriptionStorage = new MsmqSubscriptionStorage(storageQueue); + + storageQueue.Messages.Add(subscriptionMessage); + + subscriptionStorage.Init(); + + var subscribers = await subscriptionStorage.GetSubscriberAddressesForMessage(new[] + { + new MessageType("SomeMessage", "2.0.0") + }, new ContextBag()); + + Assert.AreEqual("subscriberA", subscribers.Single().Endpoint); + } + + static MsmqSubscriptionStorage CreateAndInit(FakeStorageQueue queue) + { + var storage = new MsmqSubscriptionStorage(queue); + storage.Init(); + return storage; + } + + class SomeMessage : IMessage + { + } + + class OtherMessage : IMessage + { + } + + class FakeStorageQueue : IMsmqSubscriptionStorageQueue + { + public readonly List Messages = new List(); + + DateTime arrivedTime = DateTime.Now; + + public IEnumerable GetAllMessages() + { + return Messages.ToArray(); + } + + public string Send(string body, string label) + { + var id = Guid.NewGuid().ToString(); + + Messages.Add(new MsmqSubscriptionMessage + { + ArrivedTime = arrivedTime = arrivedTime.AddMilliseconds(1), + Body = body, + Label = label, + Id = id + }); + + return id; + } + + public void TryReceiveById(string messageId) + { + Messages.RemoveAll(m => m.Id == messageId); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/MsmqUtilitiesTests.cs b/src/NServiceBus.Core.Tests/Msmq/MsmqUtilitiesTests.cs index 98db6113766..1fd473a891d 100644 --- a/src/NServiceBus.Core.Tests/Msmq/MsmqUtilitiesTests.cs +++ b/src/NServiceBus.Core.Tests/Msmq/MsmqUtilitiesTests.cs @@ -1,8 +1,13 @@ namespace NServiceBus.Core.Tests.Msmq { using System; + using System.Collections.Generic; using System.Messaging; + using DeliveryConstraints; + using NServiceBus.Performance.TimeToBeReceived; + using Transport; using NUnit.Framework; + using Support; [TestFixture] public class MsmqUtilitiesTests @@ -10,48 +15,76 @@ public class MsmqUtilitiesTests [Test] public void Should_convert_a_message_back_even_if_special_characters_are_contained_in_the_headers() { - var expected = String.Format("Can u see this '{0}' character!", (char)0x19); - var transportMessage = new TransportMessage(); - transportMessage.Headers.Add("NServiceBus.ExceptionInfo.Message", expected); - - var message = MsmqUtilities.Convert(transportMessage); - var result = MsmqUtilities.Convert(message); - - Assert.AreEqual(expected, result.Headers["NServiceBus.ExceptionInfo.Message"]); + var expected = $"Can u see this '{(char) 0x19}' character."; + + var message = MsmqUtilities.Convert(new OutgoingMessage("message id", new Dictionary + { + {"NServiceBus.ExceptionInfo.Message", expected} + }, new byte[0]), new List()); + var headers = MsmqUtilities.ExtractHeaders(message); + + Assert.AreEqual(expected, headers["NServiceBus.ExceptionInfo.Message"]); } [Test] public void Should_convert_message_headers_that_contain_nulls_at_the_end() { - var expected = "Hello World!"; - var transportMessage = new TransportMessage(); - transportMessage.Headers.Add("NServiceBus.ExceptionInfo.Message", expected); + var expected = "Hello World"; Console.Out.WriteLine(sizeof(char)); - var message = MsmqUtilities.Convert(transportMessage); - var bufferWithNulls = new byte[message.Extension.Length + (10 * sizeof(char))]; - - Buffer.BlockCopy(message.Extension, 0, bufferWithNulls, 0, bufferWithNulls.Length - (10 * sizeof(char))); - + var message = MsmqUtilities.Convert(new OutgoingMessage("message id", new Dictionary + { + {"NServiceBus.ExceptionInfo.Message", expected} + }, new byte[0]), new List()); + var bufferWithNulls = new byte[message.Extension.Length + 10*sizeof(char)]; + + Buffer.BlockCopy(message.Extension, 0, bufferWithNulls, 0, bufferWithNulls.Length - 10*sizeof(char)); + message.Extension = bufferWithNulls; - var result = MsmqUtilities.Convert(message); + var headers = MsmqUtilities.ExtractHeaders(message); - Assert.AreEqual(expected, result.Headers["NServiceBus.ExceptionInfo.Message"]); + Assert.AreEqual(expected, headers["NServiceBus.ExceptionInfo.Message"]); } [Test] - public void Should_fetch_the_replytoaddress_from_responsequeue_for_backwards_compatibility() + public void Should_fetch_the_replyToAddress_from_responsequeue_for_backwards_compatibility() { - var transportMessage = new TransportMessage(); + var message = MsmqUtilities.Convert( + new OutgoingMessage("message id", new Dictionary(), new byte[0]), + new List()); - - var message = MsmqUtilities.Convert(transportMessage); + message.ResponseQueue = new MessageQueue(new MsmqAddress("local", RuntimeEnvironment.MachineName).FullPath); + var headers = MsmqUtilities.ExtractHeaders(message); - message.ResponseQueue = new MessageQueue( MsmqUtilities.GetReturnAddress("local", "destination")); - var result = MsmqUtilities.Convert(message); + Assert.AreEqual("local@" + RuntimeEnvironment.MachineName, headers[Headers.ReplyToAddress]); + } + + [Test] + public void Should_use_the_TTBR_in_the_send_options_if_set() + { + var deliveryConstraints = new List + { + new DiscardIfNotReceivedBefore(TimeSpan.FromDays(1)) + }; + + var message = MsmqUtilities.Convert(new OutgoingMessage("message id", new Dictionary(), new byte[0]), deliveryConstraints); + + Assert.AreEqual(TimeSpan.FromDays(1), message.TimeToBeReceived); + } + + + [Test] + public void Should_use_the_non_durable_setting() + { + var nonDurableDeliveryConstraint = new List + { + new NonDurableDelivery() + }; + var durableDeliveryConstraint = new List(); - Assert.AreEqual("local@"+Environment.MachineName, result.Headers[Headers.ReplyToAddress]); + Assert.False(MsmqUtilities.Convert(new OutgoingMessage("message id", new Dictionary(), new byte[0]), nonDurableDeliveryConstraint).Recoverable); + Assert.True(MsmqUtilities.Convert(new OutgoingMessage("message id", new Dictionary(), new byte[0]), durableDeliveryConstraint).Recoverable); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/TimeToBeRceivedOverrideCheckerTest.cs b/src/NServiceBus.Core.Tests/Msmq/TimeToBeRceivedOverrideCheckerTest.cs deleted file mode 100644 index 10f7631ab5f..00000000000 --- a/src/NServiceBus.Core.Tests/Msmq/TimeToBeRceivedOverrideCheckerTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace NServiceBus.Core.Tests.Msmq -{ - using System; - using NServiceBus.Transports.Msmq; - using NUnit.Framework; - - [TestFixture] - public class TimeToBeRceivedOverrideCheckerTest - { - [Test] - public void Should_not_throw_on_non_Msmq() - { - Assert.DoesNotThrow(() => - { - TimeToBeRceivedOverrideChecker.Check(usingMsmq: false, isTransactional: false, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: false); - }); - } - - [Test] - public void Should_not_throw_on_non_transactional() - { - Assert.DoesNotThrow(() => - { - TimeToBeRceivedOverrideChecker.Check(usingMsmq: true, isTransactional: false, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: false); - }); - } - - [Test] - public void Should_not_throw_on_enabled_outbox() - { - Assert.DoesNotThrow(() => - { - TimeToBeRceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: true, auditTTBROverridden: false, forwardTTBROverridden: false); - }); - } - - - [Test] - public void Should_throw_on_overridden_audit_TimeToBeReceived() - { - var exception = Assert.Throws(() => - { - TimeToBeRceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: false, auditTTBROverridden: true, forwardTTBROverridden: false); - }); - - Assert.AreEqual("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ.", exception.Message); - } - - [Test] - public void Should_throw_on_overridden_TimeToBeReceivedOnForwardedMessages() - { - var exception = Assert.Throws(() => - { - TimeToBeRceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: true); - }); - - Assert.AreEqual("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ.", exception.Message); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Msmq/TimeToBeReceivedOverrideCheckerTest.cs b/src/NServiceBus.Core.Tests/Msmq/TimeToBeReceivedOverrideCheckerTest.cs new file mode 100644 index 00000000000..07fd55c833c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Msmq/TimeToBeReceivedOverrideCheckerTest.cs @@ -0,0 +1,46 @@ +namespace NServiceBus.Core.Tests.Msmq +{ + using NUnit.Framework; + + [TestFixture] + public class TimeToBeReceivedOverrideCheckerTest + { + [Test] + public void Should_succeed_on_non_Msmq() + { + var result = TimeToBeReceivedOverrideChecker.Check(usingMsmq: false, isTransactional: false, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: false); + Assert.IsTrue(result.Succeeded); + } + + [Test] + public void Should_succeed_on_non_transactional() + { + var result = TimeToBeReceivedOverrideChecker.Check(usingMsmq: true, isTransactional: false, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: false); + Assert.IsTrue(result.Succeeded); + } + + [Test] + public void Should_succeed_on_enabled_outbox() + { + var result = TimeToBeReceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: true, auditTTBROverridden: false, forwardTTBROverridden: false); + Assert.IsTrue(result.Succeeded); + } + + + [Test] + public void Should_fail_on_overridden_audit_TimeToBeReceived() + { + var result = TimeToBeReceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: false, auditTTBROverridden: true, forwardTTBROverridden: false); + Assert.IsFalse(result.Succeeded); + Assert.AreEqual("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ.", result.ErrorMessage); + } + + [Test] + public void Should_fail_on_overridden_TimeToBeReceivedOnForwardedMessages() + { + var result = TimeToBeReceivedOverrideChecker.Check(usingMsmq: true, isTransactional: true, outBoxRunning: false, auditTTBROverridden: false, forwardTTBROverridden: true); + Assert.IsFalse(result.Succeeded); + Assert.AreEqual("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ.", result.ErrorMessage); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/NServiceBus.Core.Tests.csproj b/src/NServiceBus.Core.Tests/NServiceBus.Core.Tests.csproj index 800bc3fce10..bcd28a14eab 100644 --- a/src/NServiceBus.Core.Tests/NServiceBus.Core.Tests.csproj +++ b/src/NServiceBus.Core.Tests/NServiceBus.Core.Tests.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,7 +9,7 @@ Properties NServiceBus.Core.Tests NServiceBus.Core.Tests - v4.5 + v4.5.2 512 true ..\Test.snk @@ -31,8 +31,8 @@ false - pdbonly - true + full + false bin\Release\ TRACE prompt @@ -41,28 +41,44 @@ 618,67 true false + true - - ..\packages\ApprovalTests.3.0.8\lib\net40\ApprovalTests.dll + + ..\packages\ApprovalTests.3.0.11\lib\net40\ApprovalTests.dll + True - - ..\packages\ApprovalUtilities.3.0.8\lib\net45\ApprovalUtilities.dll + + ..\packages\ApprovalUtilities.3.0.11\lib\net45\ApprovalUtilities.dll + True - - ..\packages\ApprovalUtilities.3.0.8\lib\net45\ApprovalUtilities.Net45.dll + + ..\packages\ApprovalUtilities.3.0.11\lib\net45\ApprovalUtilities.Net45.dll + True - - TestDlls\NServiceBus.NewCompilerBits.dll + + + ..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.dll + True - - TestDlls\NServiceBus.OldCompilerBits.dll + + ..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Mdb.dll + True - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + ..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Pdb.dll + True - - ..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll + + ..\packages\Mono.Cecil.0.9.6.1\lib\net45\Mono.Cecil.Rocks.dll + True + + + ..\packages\NuDoq.1.2.5\lib\net35\NuDoq.dll + + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True @@ -70,18 +86,6 @@ - - ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll - - - ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll - - - ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll - - - ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll - @@ -90,51 +94,140 @@ - - Lib\TestAssembly.dll - - + + + + + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - - - - - - - + + + + + + + + - - + + + @@ -143,26 +236,22 @@ + - - - + - - + - - @@ -170,51 +259,33 @@ - - - - - - - - - - - - - - + + - - - - - - @@ -234,48 +305,31 @@ - + + - - - - - - - - + + - - - - - - - + + + + + + - - - - - - - - - - - @@ -291,23 +345,59 @@ {dd48b2d0-e996-412d-9157-821ed8b17a9d} NServiceBus.Core + + {80105366-8ef9-494d-a296-e100e82224cf} + NServiceBus.Testing.Fakes + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + + + PreserveNewest - - + + PreserveNewest - + + + + + + + + diff --git a/src/NServiceBus.Core.Tests/ObservableTests.cs b/src/NServiceBus.Core.Tests/ObservableTests.cs deleted file mode 100644 index c252c4529ab..00000000000 --- a/src/NServiceBus.Core.Tests/ObservableTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus.Core.Tests -{ - using System; - using System.Reactive.Concurrency; - using System.Reactive.Linq; - using NUnit.Framework; - - [TestFixture] - public class ObservableTests - { - [Test] - public void Observable_Can_Be_Disposed_Before_Subscription() - { - var observable = new Observable(); - - var subscription = observable.SubscribeOn(Scheduler.Default).Subscribe( - f=> {}); - - observable.Dispose(); - subscription.Dispose(); - } - - class Foo { } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Outbox/FakeOutboxStorage.cs b/src/NServiceBus.Core.Tests/Outbox/FakeOutboxStorage.cs deleted file mode 100644 index efe26d3513e..00000000000 --- a/src/NServiceBus.Core.Tests/Outbox/FakeOutboxStorage.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.Core.Tests.Pipeline -{ - using System.Collections.Generic; - using Outbox; - - internal class FakeOutboxStorage : IOutboxStorage - { - public OutboxMessage ExistingMessage { get; set; } - public OutboxMessage StoredMessage { get; set; } - - public bool WasDispatched { get; set; } - - public bool TryGet(string messageId, out OutboxMessage message) - { - message = null; - - if (ExistingMessage != null && ExistingMessage.MessageId == messageId) - { - message = ExistingMessage; - return true; - } - - return false; - } - - public void Store(string messageId, IEnumerable transportOperations) - { - StoredMessage = new OutboxMessage(messageId); - StoredMessage.TransportOperations.AddRange(transportOperations); - } - - public void SetAsDispatched(string messageId) - { - WasDispatched = true; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Outbox/OutboxDeduplicationBehaviorTests.cs b/src/NServiceBus.Core.Tests/Outbox/OutboxDeduplicationBehaviorTests.cs deleted file mode 100644 index e589c33f99d..00000000000 --- a/src/NServiceBus.Core.Tests/Outbox/OutboxDeduplicationBehaviorTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace NServiceBus.Core.Tests.Pipeline -{ - using System; - using System.Transactions; - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - using Outbox; - using Unicast.Transport; - - [TestFixture] - public class OutboxDeduplicationBehaviorTests - { - - [Test] - public void Should_shortcut_the_pipeline_if_existing_message_is_found() - { - var incomingTransportMessage = new TransportMessage(); - - fakeOutbox.ExistingMessage = new OutboxMessage(incomingTransportMessage.Id); - - var context = new IncomingContext(null, incomingTransportMessage); - - Invoke(context); - - Assert.Null(fakeOutbox.StoredMessage); - } - - [Test] - public void Should_not_dispatch_the_message_if_handle_current_message_later_was_called() - { - var incomingTransportMessage = new TransportMessage(); - - - var context = new IncomingContext(null, incomingTransportMessage) - { - handleCurrentMessageLaterWasCalled = true - }; - - Invoke(context); - - Assert.False(fakeOutbox.WasDispatched); - } - - [SetUp] - public void SetUp() - { - fakeOutbox = new FakeOutboxStorage(); - - behavior = new OutboxDeduplicationBehavior - { - OutboxStorage = fakeOutbox, - TransactionSettings = new TransactionSettings(true, TimeSpan.FromSeconds(30), IsolationLevel.ReadCommitted, 5, false,false) - }; - } - - void Invoke(IncomingContext context, bool shouldAbort = false) - { - behavior.Invoke(context, () => - { - if (shouldAbort) - { - Assert.Fail("Pipeline should be aborted"); - } - }); - } - - FakeOutboxStorage fakeOutbox; - OutboxDeduplicationBehavior behavior; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Outbox/OutboxRecordBehaviorTests.cs b/src/NServiceBus.Core.Tests/Outbox/OutboxRecordBehaviorTests.cs deleted file mode 100644 index 5e650a8d025..00000000000 --- a/src/NServiceBus.Core.Tests/Outbox/OutboxRecordBehaviorTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.Core.Tests.Pipeline -{ - using NServiceBus.Pipeline.Contexts; - using NUnit.Framework; - using Outbox; - - [TestFixture] - public class OutboxRecordBehaviorTests - { - [Test] - public void Should_not_store_the_message_if_handle_current_message_later_was_called() - { - var incomingTransportMessage = new TransportMessage(); - - var context = new IncomingContext(null, incomingTransportMessage) - { - handleCurrentMessageLaterWasCalled = true - }; - - context.Set(new OutboxMessage("SomeId")); - - Invoke(context); - - Assert.Null(fakeOutbox.StoredMessage); - } - - [SetUp] - public void SetUp() - { - fakeOutbox = new FakeOutboxStorage(); - - behavior = new OutboxRecordBehavior - { - OutboxStorage = fakeOutbox - }; - } - - void Invoke(IncomingContext context, bool shouldAbort = false) - { - behavior.Invoke(context, () => - { - if (shouldAbort) - { - Assert.Fail("Pipeline should be aborted"); - } - }); - } - - FakeOutboxStorage fakeOutbox; - OutboxRecordBehavior behavior; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Performance/TimeToBeReceived/TimeToBeReceivedAttributeTests.cs b/src/NServiceBus.Core.Tests/Performance/TimeToBeReceived/TimeToBeReceivedAttributeTests.cs new file mode 100644 index 00000000000..2bc062d86a2 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Performance/TimeToBeReceived/TimeToBeReceivedAttributeTests.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.Core.Tests.Performance.TimeToBeReceived +{ + using System; + using NUnit.Framework; + + public class TimeToBeReceivedAttributeTests + { + [Test] + public void Should_use_TimeToBeReceived_from_bottom_of_tree_when_initialized() + { + var mappings = new TimeToBeReceivedMappings(new[] + { + typeof(InheritedClassWithAttribute) + }, TimeToBeReceivedMappings.DefaultConvention, true); + + TimeSpan timeToBeReceived; + + Assert.True(mappings.TryGetTimeToBeReceived(typeof(InheritedClassWithAttribute), out timeToBeReceived)); + Assert.AreEqual(TimeSpan.FromSeconds(2), timeToBeReceived); + } + + [Test] + public void Should_use_inherited_TimeToBeReceived_when_initialized() + { + var mappings = new TimeToBeReceivedMappings(new[] + { + typeof(InheritedClassWithNoAttribute) + }, TimeToBeReceivedMappings.DefaultConvention, true); + + TimeSpan timeToBeReceived; + + Assert.True(mappings.TryGetTimeToBeReceived(typeof(InheritedClassWithNoAttribute), out timeToBeReceived)); + Assert.AreEqual(TimeSpan.FromSeconds(1), timeToBeReceived); + } + + [Test] + public void Should_throw_when_discard_before_received_not_supported_when_initialized() + { + Assert.That(() => new TimeToBeReceivedMappings(new[] + { + typeof(InheritedClassWithNoAttribute) + }, TimeToBeReceivedMappings.DefaultConvention, doesTransportSupportDiscardIfNotReceivedBefore: false), Throws.Exception.Message.StartWith("Messages with TimeToBeReceived found but the selected transport does not support this type of restriction")); + } + + [Test] + public void Should_use_TimeToBeReceived_from_bottom_of_tree_when_tryget() + { + var mappings = new TimeToBeReceivedMappings(new Type[] + { + }, TimeToBeReceivedMappings.DefaultConvention, true); + + TimeSpan timeToBeReceived; + + Assert.True(mappings.TryGetTimeToBeReceived(typeof(InheritedClassWithAttribute), out timeToBeReceived)); + Assert.AreEqual(TimeSpan.FromSeconds(2), timeToBeReceived); + } + + [Test] + public void Should_use_inherited_TimeToBeReceived_when_tryget() + { + var mappings = new TimeToBeReceivedMappings(new Type[] + { + }, TimeToBeReceivedMappings.DefaultConvention, true); + + TimeSpan timeToBeReceived; + + Assert.True(mappings.TryGetTimeToBeReceived(typeof(InheritedClassWithNoAttribute), out timeToBeReceived)); + Assert.AreEqual(TimeSpan.FromSeconds(1), timeToBeReceived); + } + + [Test] + public void Should_throw_when_discard_before_received_not_supported_when_tryget() + { + var mappings = new TimeToBeReceivedMappings(new Type[] + { + }, TimeToBeReceivedMappings.DefaultConvention, doesTransportSupportDiscardIfNotReceivedBefore: false); + + Assert.That(() => + { + TimeSpan ttbr; + return mappings.TryGetTimeToBeReceived(typeof(InheritedClassWithNoAttribute), out ttbr); + }, Throws.Exception.Message.StartWith("Messages with TimeToBeReceived found but the selected transport does not support this type of restriction")); + } + + [TimeToBeReceived("00:00:01")] + class BaseClass + { + } + + [TimeToBeReceived("00:00:02")] + class InheritedClassWithAttribute : BaseClass + { + } + + class InheritedClassWithNoAttribute : BaseClass + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithTwoUniquePropertiesData.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithTwoUniquePropertiesData.cs deleted file mode 100644 index 23c381ef0b2..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithTwoUniquePropertiesData.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NServiceBus.SagaPersisters.InMemory.Tests -{ - using System; - using Saga; - - class AnotherSagaWithTwoUniqueProperties:Saga - { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - - } - } - - public class AnotherSagaWithTwoUniquePropertiesData : IContainSagaData - { - public Guid Id { get; set; } - - public string Originator { get; set; } - - public string OriginalMessageId { get; set; } - - [Unique] - public string UniqueString { get; set; } - [Unique] - public int UniqueInt { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithUniquePropertyData.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithUniquePropertyData.cs new file mode 100644 index 00000000000..f08f97b6361 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSagaWithUniquePropertyData.cs @@ -0,0 +1,28 @@ +namespace NServiceBus.SagaPersisters.InMemory.Tests +{ + using System; + using System.Threading.Tasks; + + class AnotherSagaTwoUniqueProperty : Saga, IAmStartedByMessages + { + public Task Handle(M1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.UniqueString).ToSaga(s => s.UniqueString); + } + } + + class M1 + { + public string UniqueString { get; set; } + } + + public class AnotherSagaWithUniquePropertyData : ContainSagaData + { + public string UniqueString { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSimpleSagaEntity.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSimpleSagaEntity.cs deleted file mode 100644 index c38574aac01..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/AnotherSimpleSagaEntity.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus.SagaPersisters.InMemory.Tests -{ - using System; - using Saga; - - public class AnotherSimpleSagaEntity : IContainSagaData - { - public Guid Id { get; set; } - public string Originator { get; set; } - public string OriginalMessageId { get; set; } - - public string ProductSource { get; set; } - public DateTime ProductExpirationDate { get; set; } - public decimal ProductCost { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemoryOutboxPersisterTests.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemoryOutboxPersisterTests.cs index 85630cd8e71..bd774077480 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemoryOutboxPersisterTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemoryOutboxPersisterTests.cs @@ -1,57 +1,61 @@ namespace NServiceBus.Persistence.InMemory.Tests { using System; - using System.Collections.Generic; using System.Linq; - using NServiceBus.InMemory.Outbox; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; using Outbox; [TestFixture] class InMemoryOutboxPersisterTests { - [Test] - public void Should_clear_operations_on_dispatched_messages() + public async Task Should_clear_operations_on_dispatched_messages() { var storage = new InMemoryOutboxStorage(); var messageId = "myId"; - - storage.Store(messageId,new List{new TransportOperation("x",null,null,null)}); - OutboxMessage message; + var messageToStore = new OutboxMessage(messageId, new[] { new TransportOperation("x", null, null, null) }); + using (var transaction = await storage.BeginTransaction(new ContextBag())) + { + await storage.Store(messageToStore, transaction, new ContextBag()); - storage.SetAsDispatched(messageId); + await transaction.Commit(); + } - storage.TryGet(messageId, out message); + await storage.SetAsDispatched(messageId, new ContextBag()); + var message = await storage.Get(messageId, new ContextBag()); Assert.False(message.TransportOperations.Any()); } - - [Test] - public void Should_not_remove_non_dispatched_messages() + public async Task Should_not_remove_non_dispatched_messages() { var storage = new InMemoryOutboxStorage(); var messageId = "myId"; - storage.Store(messageId, new List { new TransportOperation("x", null, null, null) }); + var messageToStore = new OutboxMessage(messageId, new[] { new TransportOperation("x", null, null, null) }); - OutboxMessage message; + using (var transaction = await storage.BeginTransaction(new ContextBag())) + { + await storage.Store(messageToStore, transaction, new ContextBag()); + + await transaction.Commit(); + } storage.RemoveEntriesOlderThan(DateTime.UtcNow); - Assert.True(storage.TryGet(messageId, out message)); + var message = await storage.Get(messageId, new ContextBag()); + Assert.NotNull(message); } - - [Test] - public void Should_clear_dispatched_messages_after_given_expiry() + public async Task Should_clear_dispatched_messages_after_given_expiry() { var storage = new InMemoryOutboxStorage(); @@ -59,19 +63,68 @@ public void Should_clear_dispatched_messages_after_given_expiry() var beforeStore = DateTime.UtcNow; - storage.Store(messageId, new List { new TransportOperation("x", null, null, null) }); + var messageToStore = new OutboxMessage(messageId, new[] { new TransportOperation("x", null, null, null) }); + using (var transaction = await storage.BeginTransaction(new ContextBag())) + { + await storage.Store(messageToStore, transaction, new ContextBag()); + + await transaction.Commit(); + } - OutboxMessage message; + // Account for the low resolution of DateTime.UtcNow. + var afterStore = DateTime.UtcNow.AddTicks(1); - storage.SetAsDispatched(messageId); + await storage.SetAsDispatched(messageId, new ContextBag()); storage.RemoveEntriesOlderThan(beforeStore); - - Assert.True(storage.TryGet(messageId, out message)); - storage.RemoveEntriesOlderThan(DateTime.UtcNow); + var message = await storage.Get(messageId, new ContextBag()); + Assert.NotNull(message); + + storage.RemoveEntriesOlderThan(afterStore); + + message = await storage.Get(messageId, new ContextBag()); + Assert.Null(message); + } + + [Test] + public async Task Should_not_store_when_transaction_not_commited() + { + var storage = new InMemoryOutboxStorage(); + + var messageId = "myId"; + + var contextBag = new ContextBag(); + using (var transaction = await storage.BeginTransaction(contextBag)) + { + var messageToStore = new OutboxMessage(messageId, new[] { new TransportOperation("x", null, null, null) }); + await storage.Store(messageToStore, transaction, contextBag); + + // do not commit + } + + var message = await storage.Get(messageId, new ContextBag()); + Assert.Null(message); + } + + [Test] + public async Task Should_store_when_transaction_commited() + { + var storage = new InMemoryOutboxStorage(); + + var messageId = "myId"; + + var contextBag = new ContextBag(); + using (var transaction = await storage.BeginTransaction(contextBag)) + { + var messageToStore = new OutboxMessage(messageId, new[] { new TransportOperation("x", null, null, null) }); + await storage.Store(messageToStore, transaction, contextBag); + + await transaction.Commit(); + } - Assert.False(storage.TryGet(messageId, out message)); + var message = await storage.Get(messageId, new ContextBag()); + Assert.NotNull(message); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySagaPersistenceFixture.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySagaPersistenceFixture.cs deleted file mode 100644 index bffbbd0122a..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySagaPersistenceFixture.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NServiceBus.SagaPersisters.InMemory.Tests -{ - using System; - using System.Collections.Generic; - using NServiceBus.InMemory.SagaPersister; - using NServiceBus.Saga; - using NServiceBus.Sagas; - using NUnit.Framework; - - abstract class InMemorySagaPersistenceFixture - { - protected InMemorySagaPersister persister; - protected List sagaTypes = new List(); - - protected void RegisterSaga() where TSaga : Saga - { - sagaTypes.Add(typeof(TSaga)); - } - - [SetUp] - public void SetUp() - { - persister = new InMemorySagaPersister(new SagaMetaModel(TypeBasedSagaMetaModel.Create(sagaTypes,new Conventions()))); - - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySubscriptionStorageTests.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySubscriptionStorageTests.cs index 6c1a4a431a7..ea0414e7b55 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySubscriptionStorageTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/InMemorySubscriptionStorageTests.cs @@ -1,30 +1,28 @@ -namespace NServiceBus.Persistence.InMemory.Tests +namespace NServiceBus.Persistence.InMemory.Tests { using System.Linq; - using NServiceBus.InMemory.SubscriptionStorage; - using NServiceBus.Unicast.Subscriptions; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; [TestFixture] class InMemorySubscriptionStorageTests { [Test] - public void Should_ignore_message_version_on_subscriptions() + public async Task Should_ignore_message_version_on_subscriptions() { - ISubscriptionStorage storage = new InMemorySubscriptionStorage(); + var storage = new InMemorySubscriptionStorage(); - storage.Subscribe(new Address("subscriberA", "subscriberA"), new[] - { - new MessageType("SomeMessage", "1.0.0") - }); + await storage.Subscribe(new Subscriber("subscriberA@server1", "subscriberA"), new MessageType("SomeMessage", "1.0.0"), new ContextBag()); - var subscribers = storage.GetSubscriberAddressesForMessage(new[] + var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { new MessageType("SomeMessage", "2.0.0") - }); + }, new ContextBag()); - Assert.AreEqual("subscriberA", subscribers.Single().Queue); + Assert.AreEqual("subscriberA", subscribers.Single().Endpoint); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaMetadataHelper.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaMetadataHelper.cs new file mode 100644 index 00000000000..7aa1c708821 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaMetadataHelper.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.SagaPersisters.InMemory.Tests +{ + using Sagas; + + class SagaMetadataHelper + { + public static SagaCorrelationProperty GetMetadata(IContainSagaData entity) + { + var metadata = SagaMetadata.Create(typeof(T)); + + SagaMetadata.CorrelationPropertyMetadata correlatedProp; + if (!metadata.TryGetCorrelationProperty(out correlatedProp)) + { + return SagaCorrelationProperty.None; + } + var prop = entity.GetType().GetProperty(correlatedProp.Name); + + var value = prop.GetValue(entity); + + return new SagaCorrelationProperty(correlatedProp.Name, value); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithTwoUniquePropertiesData.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithTwoUniquePropertiesData.cs deleted file mode 100644 index 3d1e073aaa8..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithTwoUniquePropertiesData.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NServiceBus.SagaPersisters.InMemory.Tests -{ - using System; - using Saga; - - class SagaWithTwoUniqueProperties:Saga - { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - - } - } - - public class SagaWithTwoUniquePropertiesData : IContainSagaData - { - public Guid Id { get; set; } - - public string Originator { get; set; } - - public string OriginalMessageId { get; set; } - - [Unique] - public string UniqueString { get; set; } - [Unique] - public int UniqueInt { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithUniquePropertyData.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithUniquePropertyData.cs index c1cb448b83f..ec0c0a80634 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithUniquePropertyData.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/SagaWithUniquePropertyData.cs @@ -1,24 +1,27 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; - using Saga; + using System.Threading.Tasks; - class SagaWithUniqueProperty : Saga + class SagaWithUniqueProperty : Saga, IAmStartedByMessages { + public Task Handle(M12 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - + mapper.ConfigureMapping(m => m.UniqueString).ToSaga(s => s.UniqueString); } } - public class SagaWithUniquePropertyData : IContainSagaData + public class SagaWithUniquePropertyData : ContainSagaData { - public Guid Id { get; set; } - - public string Originator { get; set; } - - public string OriginalMessageId { get; set; } + public string UniqueString { get; set; } + } - [Unique] + class M12 + { public string UniqueString { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntity.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntity.cs deleted file mode 100644 index b5dbe38a29b..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntity.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.SagaPersisters.InMemory.Tests -{ - using System; - using Saga; - - class SimpleSagaEntitySaga:Saga - { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - - } - } - public class SimpleSagaEntity : IContainSagaData - { - public Guid Id { get; set; } - public string Originator { get; set; } - public string OriginalMessageId { get; set; } - - public string OrderSource { get; set; } - public DateTime OrderExpirationDate { get; set; } - public decimal OrderCost { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntitySaga.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntitySaga.cs new file mode 100644 index 00000000000..dad7c596da9 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/SimpleSagaEntitySaga.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.SagaPersisters.InMemory.Tests +{ + using System; + using System.Threading.Tasks; + + class SimpleSagaEntitySaga : Saga, IAmStartedByMessages + { + public Task Handle(StartMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.OrderSource); + } + } + + public class SimpleSagaEntity : ContainSagaData + { + public string OrderSource { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/TestSagaData.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/TestSagaData.cs index cd6ec7e7c9e..60333b773f0 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/TestSagaData.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/TestSagaData.cs @@ -2,22 +2,29 @@ { using System; using System.Collections.Generic; - using Saga; + using System.Threading.Tasks; - class TestSaga:Saga + class TestSaga : Saga, IAmStartedByMessages { + public Task Handle(StartMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); } } - public class TestSagaData : IContainSagaData - { - public Guid Id { get; set; } - public string Originator { get; set; } + class StartMessage + { + public string SomeId { get; set; } + } - public string OriginalMessageId { get; set; } + public class TestSagaData : ContainSagaData + { + public string SomeId { get; set; } = "Test"; public RelatedClass RelatedClass { get; set; } @@ -30,7 +37,6 @@ public class TestSagaData : IContainSagaData public TestComponent TestComponent { get; set; } public PolymorphicPropertyBase PolymorphicRelatedProperty { get; set; } - } public class PolymorphicProperty : PolymorphicPropertyBase @@ -45,7 +51,8 @@ public class PolymorphicPropertyBase public enum StatusEnum { - SomeStatus, AnotherStatus + SomeStatus, + AnotherStatus } public class TestComponent @@ -59,7 +66,6 @@ public class OrderLine public Guid Id { get; set; } public Guid ProductId { get; set; } - } public class RelatedClass diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_the_InMemory_persister.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_the_InMemory_persister.cs index fb630f54a56..a16eefc1cf5 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_the_InMemory_persister.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_the_InMemory_persister.cs @@ -1,25 +1,34 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_completing_a_saga_with_the_InMemory_persister:InMemorySagaPersistenceFixture + class When_completing_a_saga_with_the_InMemory_persister { - public When_completing_a_saga_with_the_InMemory_persister() - { - RegisterSaga(); - } - [Test] - public void Should_delete_the_saga() + public async Task Should_delete_the_saga() { - var saga = new TestSagaData { Id = Guid.NewGuid() }; - - persister.Save(saga); - Assert.NotNull(persister.Get(saga.Id)); - persister.Complete(saga); - Assert.Null(persister.Get(saga.Id)); + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + var sagaData = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var deleteSession = new InMemorySynchronizedStorageSession(); + await persister.Complete(saga, deleteSession, new ContextBag()); + await deleteSession.CompleteAsync(); + var completedSaga = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + Assert.NotNull(sagaData); + Assert.Null(completedSaga); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_unique_property_with_InMemory_persister.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_unique_property_with_InMemory_persister.cs index 69646c729ba..a318dd0b80c 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_unique_property_with_InMemory_persister.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_completing_a_saga_with_unique_property_with_InMemory_persister.cs @@ -1,25 +1,33 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_completing_a_saga_with_unique_property_with_InMemory_persister:InMemorySagaPersistenceFixture + class When_completing_a_saga_with_unique_property_with_InMemory_persister { - public When_completing_a_saga_with_unique_property_with_InMemory_persister() - { - RegisterSaga(); - } - [Test] - public void Should_delete_the_saga() + public async Task Should_delete_the_saga() { var saga = new SagaWithUniquePropertyData { Id = Guid.NewGuid(), UniqueString = "whatever" }; - persister.Save(saga); - Assert.NotNull(persister.Get(saga.Id)); - persister.Complete(saga); - Assert.Null(persister.Get(saga.Id)); + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga,SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + var sagaData = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var completeSession = new InMemorySynchronizedStorageSession(); + await persister.Complete(saga, completeSession, new ContextBag()); + await completeSession.CompleteAsync(); + + var completedSagaData = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + Assert.NotNull(sagaData); + Assert.Null(completedSagaData); } } } diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_multiple_workers_retrieve_same_saga.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_multiple_workers_retrieve_same_saga.cs index 167a7c1d4e4..78af02d0ee5 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_multiple_workers_retrieve_same_saga.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_multiple_workers_retrieve_same_saga.cs @@ -2,76 +2,143 @@ { using System; using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_multiple_workers_retrieve_same_saga : InMemorySagaPersistenceFixture + class When_multiple_workers_retrieve_same_saga { - public When_multiple_workers_retrieve_same_saga() - { - RegisterSaga(); - } [Test] - public void Persister_returns_different_instance_of_saga_data() + public async Task Persister_returns_different_instance_of_saga_data() { - var saga = new TestSagaData { Id = Guid.NewGuid() }; - persister.Save(saga); + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); - var returnedSaga1 = persister.Get(saga.Id); - var returnedSaga2 = persister.Get("Id", saga.Id); + var returnedSaga1 = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + var returnedSaga2 = await persister.Get("Id", saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); Assert.AreNotSame(returnedSaga2, returnedSaga1); Assert.AreNotSame(returnedSaga1, saga); Assert.AreNotSame(returnedSaga2, saga); } [Test] - public void Save_fails_when_data_changes_between_read_and_update() + public async Task Save_fails_when_data_changes_between_read_and_update() + { + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + var returnedSaga1 = await Task.Run(() => persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag())); + var returnedSaga2 = await persister.Get("Id", saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var winningSaveSession = new InMemorySynchronizedStorageSession(); + var losingSaveSession = new InMemorySynchronizedStorageSession(); + await persister.Save(returnedSaga1, SagaMetadataHelper.GetMetadata(saga), winningSaveSession, new ContextBag()); + await persister.Save(returnedSaga2, SagaMetadataHelper.GetMetadata(saga), losingSaveSession, new ContextBag()); + + await winningSaveSession.CompleteAsync(); + + Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf().And.Message.StartsWith($"InMemorySagaPersister concurrency violation: saga entity Id[{saga.Id}] already saved.")); + } + + [Test] + public async Task Save_fails_when_data_changes_between_read_and_update_on_same_thread() { - var saga = new TestSagaData { Id = Guid.NewGuid() }; - persister.Save(saga); + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); - var returnedSaga1 = Task.Factory.StartNew(() => persister.Get(saga.Id)).Result; - var returnedSaga2 = persister.Get("Id", saga.Id); + var record = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + var staleRecord = await persister.Get("Id", saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); - persister.Save(returnedSaga1); - var exception = Assert.Throws(() => persister.Save(returnedSaga2)); - Assert.IsTrue(exception.Message.StartsWith(string.Format("InMemorySagaPersister concurrency violation: saga entity Id[{0}] already saved.", saga.Id))); + var winningSaveSession = new InMemorySynchronizedStorageSession(); + var losingSaveSession = new InMemorySynchronizedStorageSession(); + + await persister.Save(record, SagaMetadataHelper.GetMetadata(saga), winningSaveSession, new ContextBag()); + await persister.Save(staleRecord, SagaMetadataHelper.GetMetadata(saga), losingSaveSession, new ContextBag()); + + await winningSaveSession.CompleteAsync(); + + Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf().And.Message.StartsWith($"InMemorySagaPersister concurrency violation: saga entity Id[{saga.Id}] already saved.")); } [Test] - public void Save_fails_when_data_changes_between_read_and_update_on_same_thread() + public async Task Save_fails_when_writing_same_data_twice() { - var saga = new TestSagaData { Id = Guid.NewGuid() }; - persister.Save(saga); + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + var returnedSaga1 = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var winningSaveSession = new InMemorySynchronizedStorageSession(); + var losingSaveSession = new InMemorySynchronizedStorageSession(); - var record = persister.Get(saga.Id); - var staleRecord = persister.Get("Id", saga.Id); + await persister.Save(returnedSaga1, SagaMetadataHelper.GetMetadata(saga), winningSaveSession, new ContextBag()); + await persister.Save(returnedSaga1, SagaMetadataHelper.GetMetadata(saga), losingSaveSession, new ContextBag()); - persister.Save(record); - var exception = Assert.Throws(() => persister.Save(staleRecord)); - Assert.IsTrue(exception.Message.StartsWith(string.Format("InMemorySagaPersister concurrency violation: saga entity Id[{0}] already saved.", saga.Id))); + await winningSaveSession.CompleteAsync(); + + Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf().And.Message.StartsWith($"InMemorySagaPersister concurrency violation: saga entity Id[{saga.Id}] already saved.")); } [Test] - public void Save_process_is_repeatable() + public async Task Save_process_is_repeatable() { - var saga = new TestSagaData { Id = Guid.NewGuid() }; - persister.Save(saga); + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + var persister = new InMemorySagaPersister(); + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + var returnedSaga1 = await Task.Run(() => persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag())); + var returnedSaga2 = await persister.Get("Id", saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var winningSaveSession = new InMemorySynchronizedStorageSession(); + var losingSaveSession = new InMemorySynchronizedStorageSession(); + + await persister.Save(returnedSaga1, SagaMetadataHelper.GetMetadata(saga), winningSaveSession, new ContextBag()); + await persister.Save(returnedSaga2, SagaMetadataHelper.GetMetadata(saga), losingSaveSession, new ContextBag()); + + await winningSaveSession.CompleteAsync(); + Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf().And.Message.StartsWith($"InMemorySagaPersister concurrency violation: saga entity Id[{saga.Id}] already saved.")); - var returnedSaga1 = Task.Factory.StartNew(() => persister.Get(saga.Id)).Result; - var returnedSaga2 = persister.Get("Id", saga.Id); + var returnedSaga3 = await Task.Run(() => persister.Get("Id", saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag())); + var returnedSaga4 = await persister.Get(saga.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); - persister.Save(returnedSaga1); - var exceptionFromSaga2 = Assert.Throws(() => persister.Save(returnedSaga2)); - Assert.IsTrue(exceptionFromSaga2.Message.StartsWith(string.Format("InMemorySagaPersister concurrency violation: saga entity Id[{0}] already saved.", saga.Id))); + winningSaveSession = new InMemorySynchronizedStorageSession(); + losingSaveSession = new InMemorySynchronizedStorageSession(); - var returnedSaga3 = Task.Factory.StartNew(() => persister.Get("Id", saga.Id)).Result; - var returnedSaga4 = persister.Get(saga.Id); + await persister.Save(returnedSaga4, SagaMetadataHelper.GetMetadata(saga), winningSaveSession, new ContextBag()); + await persister.Save(returnedSaga3, SagaMetadataHelper.GetMetadata(saga), losingSaveSession, new ContextBag()); - persister.Save(returnedSaga4); + await winningSaveSession.CompleteAsync(); - var exceptionFromSaga3 = Assert.Throws(() => persister.Save(returnedSaga3)); - Assert.IsTrue(exceptionFromSaga3.Message.StartsWith(string.Format("InMemorySagaPersister concurrency violation: saga entity Id[{0}] already saved.", saga.Id))); + Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf().And.Message.StartsWith($"InMemorySagaPersister concurrency violation: saga entity Id[{saga.Id}] already saved.")); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_InMemory_and_an_escalated_DTC_transaction.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_InMemory_and_an_escalated_DTC_transaction.cs new file mode 100644 index 00000000000..527d4524f7e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_InMemory_and_an_escalated_DTC_transaction.cs @@ -0,0 +1,79 @@ +namespace NServiceBus.SagaPersisters.InMemory.Tests +{ + using System; + using System.Threading.Tasks; + using Extensibility; + using NUnit.Framework; + using System.Transactions; + using Transport; + + [TestFixture] + class When_persisting_a_saga_with_InMemory_and_an_escalated_DTC_transaction + { + [Test] + public async Task Save_fails_when_data_changes_between_concurrent_instances() + { + var saga = new TestSagaData + { + Id = Guid.NewGuid() + }; + + var persister = new InMemorySagaPersister(); + var storageAdapter = new InMemoryTransactionalSynchronizedStorageAdapter(); + var insertSession = new InMemorySynchronizedStorageSession(); + + await persister.Save(saga, SagaMetadataHelper.GetMetadata(saga), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + Assert.That(async () => + { + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + Transaction.Current.EnlistDurable(EnlistmentWhichEnforcesDtcEscalation.Id, new EnlistmentWhichEnforcesDtcEscalation(), EnlistmentOptions.None); + + var transportTransaction = new TransportTransaction(); + transportTransaction.Set(Transaction.Current); + + var unenlistedSession = new InMemorySynchronizedStorageSession(); + + var enlistedSession = await storageAdapter.TryAdapt(transportTransaction, new ContextBag()); + + var unenlistedRecord = await persister.Get(saga.Id, unenlistedSession, new ContextBag()); + var enlistedRecord = await persister.Get("Id", saga.Id, enlistedSession, new ContextBag()); + + await persister.Save(unenlistedRecord, SagaMetadataHelper.GetMetadata(saga), unenlistedSession, new ContextBag()); + await persister.Save(enlistedRecord, SagaMetadataHelper.GetMetadata(saga), enlistedSession, new ContextBag()); + + await unenlistedSession.CompleteAsync(); + + tx.Complete(); + } + }, Throws.Exception.TypeOf()); + } + } + + public class EnlistmentWhichEnforcesDtcEscalation : IEnlistmentNotification + { + public static readonly Guid Id = Guid.NewGuid(); + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + preparingEnlistment.Prepared(); + } + + public void Commit(Enlistment enlistment) + { + enlistment.Done(); + } + + public void Rollback(Enlistment enlistment) + { + enlistment.Done(); + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga.cs index 93282ce5acf..8fa8d6a2e63 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga.cs @@ -1,29 +1,49 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga:InMemorySagaPersistenceFixture + class When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga { - public When_persisting_a_saga_with_the_same_unique_property_as_a_completed_saga() - { - RegisterSaga(); - } [Test] - public void It_should_persist_successfully() + public async Task It_should_persist_successfully() { - var saga1 = new SagaWithUniquePropertyData { Id = Guid.NewGuid(), UniqueString = "whatever" }; - var saga2 = new SagaWithUniquePropertyData { Id = Guid.NewGuid(), UniqueString = "whatever" }; + var saga1 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + var saga2 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + + var persister = new InMemorySagaPersister(); + using (var session1 = new InMemorySynchronizedStorageSession()) + { + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), session1, new ContextBag()); + await persister.Complete(saga1, session1, new ContextBag()); + await session1.CompleteAsync(); + } - persister.Save(saga1); - persister.Complete(saga1); - persister.Save(saga2); - persister.Complete(saga2); - persister.Save(saga1); - persister.Complete(saga1); + using (var session2 = new InMemorySynchronizedStorageSession()) + { + await persister.Save(saga2, SagaMetadataHelper.GetMetadata(saga2), session2, new ContextBag()); + await persister.Complete(saga2, session2, new ContextBag()); + await session2.CompleteAsync(); + } + using (var session3 = new InMemorySynchronizedStorageSession()) + { + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), session3, new ContextBag()); + await persister.Complete(saga1, session3, new ContextBag()); + await session3.CompleteAsync(); + } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_another_saga.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_another_saga.cs index 3083361d27f..9bea08b4a48 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_another_saga.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_another_saga.cs @@ -1,36 +1,37 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_persisting_a_saga_with_the_same_unique_property_as_another_saga:InMemorySagaPersistenceFixture + class When_persisting_a_saga_with_the_same_unique_property_as_another_saga { - public When_persisting_a_saga_with_the_same_unique_property_as_another_saga() - { - RegisterSaga(); - RegisterSaga(); - } [Test] - public void It_should_enforce_uniqueness() + public async Task It_should_enforce_uniqueness() { - var saga1 = new SagaWithUniquePropertyData { Id = Guid.NewGuid(), UniqueString = "whatever"}; - var saga2 = new SagaWithUniquePropertyData { Id = Guid.NewGuid(), UniqueString = "whatever"}; + var saga1 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + var saga2 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; - persister.Save(saga1); - Assert.Throws(() => persister.Save(saga2)); - } - [Test] - public void It_should_enforce_uniqueness_even_for_two_unique_properties() - { - var saga1 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever", UniqueInt = 5}; - var saga2 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever1", UniqueInt = 3}; - var saga3 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever3", UniqueInt = 3 }; - - persister.Save(saga1); - persister.Save(saga2); - Assert.Throws(() => persister.Save(saga3)); - } + var persister = new InMemorySagaPersister(); + var winningSession = new InMemorySynchronizedStorageSession(); + var losingSession = new InMemorySynchronizedStorageSession(); + + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), winningSession, new ContextBag()); + await persister.Save(saga2, SagaMetadataHelper.GetMetadata(saga1), losingSession, new ContextBag()); + await winningSession.CompleteAsync(); + + Assert.That(async () => await losingSession.CompleteAsync(), Throws.InstanceOf()); + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating.cs index f019cb909a6..2eeaae82d60 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating.cs @@ -1,27 +1,42 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating:InMemorySagaPersistenceFixture + class When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating { - public When_persisting_a_saga_with_the_same_unique_property_as_the_original_value_of_another_saga_before_updating() - { - RegisterSaga(); - } [Test] - public void It_should_persist_successfully() + public async Task It_should_persist_successfully() { - var saga1 = new SagaWithUniquePropertyData{Id = Guid.NewGuid(), UniqueString = "whatever"}; - var saga2 = new SagaWithUniquePropertyData{Id = Guid.NewGuid(), UniqueString = "whatever"}; - - persister.Save(saga1); - saga1 = persister.Get(saga1.Id); + var saga1 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + var saga2 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + + var persister = new InMemorySagaPersister(); + + var firstInsertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), firstInsertSession, new ContextBag()); + await firstInsertSession.CompleteAsync(); + + var updateSession = new InMemorySynchronizedStorageSession(); + saga1 = await persister.Get(saga1.Id, updateSession, new ContextBag()); saga1.UniqueString = "whatever2"; - persister.Update(saga1); + await persister.Update(saga1, updateSession, new ContextBag()); + await updateSession.CompleteAsync(); - persister.Save(saga2); + var secondInsertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga2, SagaMetadataHelper.GetMetadata(saga2), secondInsertSession, new ContextBag()); + await secondInsertSession.CompleteAsync(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_different_sagas_with_unique_properties.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_different_sagas_with_unique_properties.cs index 7ddc2b2d5c1..1987bce1b06 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_different_sagas_with_unique_properties.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_persisting_different_sagas_with_unique_properties.cs @@ -1,29 +1,32 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_persisting_different_sagas_with_unique_properties : InMemorySagaPersistenceFixture + class When_persisting_different_sagas_with_unique_properties { - public When_persisting_different_sagas_with_unique_properties() - { - RegisterSaga(); - RegisterSaga(); - RegisterSaga(); - } [Test] - public void It_should_persist_successfully() + public async Task It_should_persist_successfully() { - var saga1 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever", UniqueInt = 5 }; - var saga2 = new AnotherSagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever", UniqueInt = 5 }; - var saga3 = new SagaWithUniquePropertyData {Id = Guid.NewGuid(), UniqueString = "whatever"}; - - + var saga1 = new SagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + var saga2 = new AnotherSagaWithUniquePropertyData + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; - persister.Save(saga1); - persister.Save(saga2); - persister.Save(saga3); + var persister = new InMemorySagaPersister(); + var transaction = new InMemorySynchronizedStorageSession(); + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), transaction, new ContextBag()); + await persister.Save(saga2, SagaMetadataHelper.GetMetadata(saga2), transaction, new ContextBag()); + await transaction.CompleteAsync(); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_saga_not_found_return_default.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_saga_not_found_return_default.cs index cddd9cfdc7f..94f4dbedb1b 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_saga_not_found_return_default.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_saga_not_found_return_default.cs @@ -1,31 +1,31 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_saga_not_found_return_default : InMemorySagaPersistenceFixture + class When_saga_not_found_return_default { - public When_saga_not_found_return_default() - { - RegisterSaga(); - } [Test] - public void Should_return_default_when_using_finding_saga_with_property() + public async Task Should_return_default_when_using_finding_saga_with_property() { - var simpleSageEntity = persister.Get("propertyNotFound", null); + var persister = new InMemorySagaPersister(); + var simpleSageEntity = await persister.Get("propertyNotFound", "someValue", new InMemorySynchronizedStorageSession(), new ContextBag()); Assert.IsNull(simpleSageEntity); } [Test] - public void Should_return_default_when_using_finding_saga_with_id() + public async Task Should_return_default_when_using_finding_saga_with_id() { - var simpleSageEntity = persister.Get(Guid.Empty); + var persister = new InMemorySagaPersister(); + var simpleSageEntity = await persister.Get(Guid.Empty, new InMemorySynchronizedStorageSession(), new ContextBag()); Assert.IsNull(simpleSageEntity); } [Test] - public void Should_return_default_when_using_finding_saga_with_id_of_another_type() + public async Task Should_return_default_when_using_finding_saga_with_id_of_another_type() { var id = Guid.NewGuid(); var simpleSagaEntity = new SimpleSagaEntity @@ -33,10 +33,16 @@ public void Should_return_default_when_using_finding_saga_with_id_of_another_typ Id = id, OrderSource = "CA" }; - persister.Save(simpleSagaEntity); - - var anotherSagaEntity = persister.Get(id); + var persister = new InMemorySagaPersister(); + var session = new InMemorySynchronizedStorageSession(); + await persister.Save(simpleSagaEntity, SagaMetadataHelper.GetMetadata(simpleSagaEntity), session, new ContextBag()); + await session.CompleteAsync(); + var anotherSagaEntity = await persister.Get(id, new InMemorySynchronizedStorageSession(), new ContextBag()); Assert.IsNull(anotherSagaEntity); } + + public class AnotherSimpleSagaEntity : ContainSagaData + { + } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_as_another_saga.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_as_another_saga.cs index 85e3a1b035a..f4ec54a2aa3 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_as_another_saga.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_as_another_saga.cs @@ -1,49 +1,32 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_updating_a_saga_with_the_same_unique_property_as_another_saga:InMemorySagaPersistenceFixture + class When_updating_a_saga_with_the_same_unique_property_as_another_saga { - public When_updating_a_saga_with_the_same_unique_property_as_another_saga() - { - RegisterSaga(); - - RegisterSaga(); - } [Test] - public void It_should_persist_successfully() + public async Task It_should_persist_successfully() { - var saga1 = new SagaWithUniquePropertyData {Id = Guid.NewGuid(), UniqueString = "whatever1"}; - var saga2 = new SagaWithUniquePropertyData {Id = Guid.NewGuid(), UniqueString = "whatever"}; - - persister.Save(saga1); - persister.Save(saga2); - - Assert.Throws(() => + var saga1 = new SagaWithUniquePropertyData { - var saga = persister.Get(saga2.Id); - saga.UniqueString = "whatever1"; - persister.Update(saga); - }); - } - - [Test] - public void It_should_persist_successfully_for_two_unique_properties() - { - var saga1 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever1", UniqueInt = 5}; - var saga2 = new SagaWithTwoUniquePropertiesData { Id = Guid.NewGuid(), UniqueString = "whatever", UniqueInt = 37}; - - persister.Save(saga1); - persister.Save(saga2); - - Assert.Throws(() => + Id = Guid.NewGuid(), + UniqueString = "whatever1" + }; + var saga2 = new SagaWithUniquePropertyData { - var saga = persister.Get(saga2.Id); - saga.UniqueInt = 5; - persister.Update(saga); - }); + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + + var persister = new InMemorySagaPersister(); + var session = new InMemorySynchronizedStorageSession(); + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), session, new ContextBag()); + await persister.Save(saga2, SagaMetadataHelper.GetMetadata(saga2), session, new ContextBag()); + await session.CompleteAsync(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_value.cs b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_value.cs index b2e3acedd25..19f8c37c3ec 100644 --- a/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_value.cs +++ b/src/NServiceBus.Core.Tests/Persistence/InMemory/When_updating_a_saga_with_the_same_unique_property_value.cs @@ -1,27 +1,32 @@ namespace NServiceBus.SagaPersisters.InMemory.Tests { using System; + using System.Threading.Tasks; + using Extensibility; using NUnit.Framework; [TestFixture] - class When_updating_a_saga_with_the_same_unique_property_value : InMemorySagaPersistenceFixture + class When_updating_a_saga_with_the_same_unique_property_value { - public When_updating_a_saga_with_the_same_unique_property_value() - { - RegisterSaga(); - } - [Test] - public void It_should_persist_successfully() + public async Task It_should_persist_successfully() { var saga1 = new SagaWithUniquePropertyData - { - Id = Guid.NewGuid(), - UniqueString = "whatever" - }; - persister.Save(saga1); - saga1 = persister.Get(saga1.Id); - persister.Update(saga1); + { + Id = Guid.NewGuid(), + UniqueString = "whatever" + }; + var persister = new InMemorySagaPersister(); + + var insertSession = new InMemorySynchronizedStorageSession(); + await persister.Save(saga1, SagaMetadataHelper.GetMetadata(saga1), insertSession, new ContextBag()); + await insertSession.CompleteAsync(); + + saga1 = await persister.Get(saga1.Id, new InMemorySynchronizedStorageSession(), new ContextBag()); + + var updateSession = new InMemorySynchronizedStorageSession(); + await persister.Update(saga1, updateSession, new ContextBag()); + await updateSession.CompleteAsync(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/Msmq/MsmqSubscriptionStorageTests.cs b/src/NServiceBus.Core.Tests/Persistence/Msmq/MsmqSubscriptionStorageTests.cs deleted file mode 100644 index e426c5d3baa..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/Msmq/MsmqSubscriptionStorageTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace NServiceBus.Core.Tests.Persistence.Msmq -{ - using System; - using System.Linq; - using System.Messaging; - using NServiceBus.Persistence.SubscriptionStorage; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; - using NUnit.Framework; - using MessageType = NServiceBus.Unicast.Subscriptions.MessageType; - - public class MsmqSubscriptionStorageTests - { - [Test] - public void Should_ignore_message_version_on_subscriptions() - { - var testQueueName = "ShouldIgnoreMessageVersionOnSubscriptions"; - var testQueueNativeAddress = Environment.MachineName + MsmqUtilities.PRIVATE + testQueueName; - - if (MessageQueue.Exists(testQueueNativeAddress)) - { - new MessageQueue(testQueueNativeAddress).Purge(); - } - else - { - MessageQueue.Create(testQueueNativeAddress); - } - - ISubscriptionStorage subscriptionStorage = new MsmqSubscriptionStorage - { - Queue = Address.Parse(testQueueName) - }; - - subscriptionStorage.Init(); - - subscriptionStorage.Subscribe(new Address("subscriberA", "server1"), new[] { new MessageType("SomeMessage", "1.0.0") }); - - - var subscribers = subscriptionStorage.GetSubscriberAddressesForMessage(new[] - { - new MessageType("SomeMessage", "2.0.0") - }); - - Assert.AreEqual("subscriberA", subscribers.Single().Queue); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs new file mode 100644 index 00000000000..e97206ac788 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtensionsTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Persistence +{ + using System; + using NServiceBus.Persistence; + using Settings; + using NUnit.Framework; + + [TestFixture] + public class When_configuring_storage_type_not_supported_by_persistence + { + [Test] + public void Should_throw_exception() + { + // ReSharper disable once ObjectCreationAsStatement + var ex = Assert.Throws(() => new PersistenceExtensions(typeof(PartialPersistence), new SettingsHolder(), typeof(StorageType.Timeouts))); + Assert.That(ex.Message, Is.StringStarting("PartialPersistence does not support storage type Timeouts.")); + } + + public class PartialPersistence : PersistenceDefinition + { + public PartialPersistence() + { + Supports(s => + { + }); + } + } + } + + [TestFixture] + public class When_configuring_storage_type_supported_by_persistence + { + [Test] + public void Should_not_throw_exception() + { + // ReSharper disable once ObjectCreationAsStatement + Assert.DoesNotThrow(() => new PersistenceExtensions(typeof(PartialPersistence), new SettingsHolder(), typeof(StorageType.Subscriptions))); + } + + public class PartialPersistence : PersistenceDefinition + { + public PartialPersistence() + { + Supports(s => + { + }); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtentionsTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceExtentionsTests.cs deleted file mode 100644 index f6a1f908d5d..00000000000 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceExtentionsTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus.Core.Tests.Persistence -{ - using System; - using NServiceBus.Persistence; - using NServiceBus.Persistence.Legacy; - using NServiceBus.Settings; - using NUnit.Framework; - - [TestFixture] - public class When_configuring_storage_type_not_supported_by_persistence - { - [Test] - public void Should_throw_exception() - { - // ReSharper disable once ObjectCreationAsStatement - var ex = Assert.Throws(() => new PersistenceExtentions(typeof(MsmqPersistence), new SettingsHolder(), typeof(StorageType.Timeouts))); - Assert.That(ex.Message, Is.StringStarting("MsmqPersistence does not support storage type Timeouts.")); - } - } - - [TestFixture] - public class When_configuring_storage_type_supported_by_persistencer - { - [Test] - public void Should_not_throw_exception() - { - // ReSharper disable once ObjectCreationAsStatement - Assert.DoesNotThrow(() => new PersistenceExtentions(typeof(MsmqPersistence), new SettingsHolder(), typeof(StorageType.Subscriptions))); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs new file mode 100644 index 00000000000..655ac3fc84f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStartupTests.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Core.Tests.Persistence +{ + using NServiceBus.Persistence; + using NUnit.Framework; + using Settings; + + [TestFixture] + public class When_no_persistence_has_been_configured + { + [Test] + public void Should_return_false_when_checking_if_persistence_supports_storage_type() + { + var settings = new SettingsHolder(); + + var supported = PersistenceStartup.HasSupportFor(settings); + + Assert.IsFalse(supported); + } + } +} diff --git a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs index 3111b2925c2..1181709dfb0 100644 --- a/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs +++ b/src/NServiceBus.Core.Tests/Persistence/PersistenceStorageMergerTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using NServiceBus.Persistence; - using NServiceBus.Settings; + using Settings; using NUnit.Framework; [TestFixture] diff --git a/src/NServiceBus.Core.Tests/Pipeline/BehaviorRegistrationsCoordinatorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/BehaviorRegistrationsCoordinatorTests.cs index a9e1a4d12fe..19b2b3cdc2c 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/BehaviorRegistrationsCoordinatorTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/BehaviorRegistrationsCoordinatorTests.cs @@ -4,8 +4,8 @@ namespace NServiceBus.Core.Tests.Pipeline using System; using System.Collections.Generic; using System.Linq; + using System.Threading.Tasks; using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; using NUnit.Framework; [TestFixture] @@ -13,13 +13,13 @@ class BehaviorRegistrationsCoordinatorTests { StepRegistrationsCoordinator coordinator; List removals; - List replacements; + List replacements; [SetUp] public void Setup() { removals = new List(); - replacements = new List(); + replacements = new List(); coordinator = new StepRegistrationsCoordinator(removals, replacements); } @@ -33,9 +33,9 @@ public void Registrations_Count() removals.Add(new RemoveStep("1")); - var model = coordinator.BuildRuntimeModel(); + var model = coordinator.BuildPipelineModelFor(); - Assert.AreEqual(2, model.Count()); + Assert.AreEqual(2, model.Count); } [Test] @@ -45,7 +45,7 @@ public void Registrations_Order() coordinator.Register("2", typeof(FakeBehavior), "2"); coordinator.Register("3", typeof(FakeBehavior), "3"); - var model = coordinator.BuildRuntimeModel().ToList(); + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual("1", model[0].StepId); Assert.AreEqual("2", model[1].StepId); @@ -59,10 +59,10 @@ public void Registrations_Replace() coordinator.Register("2", typeof(FakeBehavior), "2"); coordinator.Register("3", typeof(FakeBehavior), "3"); - replacements.Add(new ReplaceBehavior("1", typeof(ReplacedBehavior), "new")); - replacements.Add(new ReplaceBehavior("2", typeof(ReplacedBehavior))); + replacements.Add(new ReplaceStep("1", typeof(ReplacedBehavior), "new")); + replacements.Add(new ReplaceStep("2", typeof(ReplacedBehavior))); - var model = coordinator.BuildRuntimeModel().ToList(); + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual(typeof(ReplacedBehavior).FullName, model[0].BehaviorType.FullName); Assert.AreEqual("new", model[0].Description); @@ -79,8 +79,8 @@ public void Registrations_Order_with_befores_and_afters() coordinator.Register(new MyCustomRegistration("1.5", "2", "1")); coordinator.Register(new MyCustomRegistration("2.5", "3", "2")); coordinator.Register(new MyCustomRegistration("3.5", null, "3")); - - var model = coordinator.BuildRuntimeModel().ToList(); + + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual("1", model[0].StepId); Assert.AreEqual("1.5", model[1].StepId); @@ -100,7 +100,7 @@ public void Registrations_Order_with_befores_only() coordinator.Register(new MyCustomRegistration("1.5", "2,3", null)); coordinator.Register(new MyCustomRegistration("2.5", "3", null)); - var model = coordinator.BuildRuntimeModel().ToList(); + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual("1", model[0].StepId); Assert.AreEqual("1.5", model[1].StepId); @@ -120,7 +120,7 @@ public void Registrations_Order_with_multi_afters() coordinator.Register(new MyCustomRegistration("2.5", "3", "2,1")); coordinator.Register(new MyCustomRegistration("3.5", null, "1,2,3")); - var model = coordinator.BuildRuntimeModel().ToList(); + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual("1", model[0].StepId); Assert.AreEqual("1.5", model[1].StepId); @@ -141,7 +141,7 @@ public void Registrations_Order_with_afters_only() coordinator.Register(new MyCustomRegistration("1.6", "2", "1.5")); coordinator.Register(new MyCustomRegistration("1.1", "1.5", "1")); - var model = coordinator.BuildRuntimeModel().ToList(); + var model = coordinator.BuildPipelineModelFor().ToList(); Assert.AreEqual("1", model[0].StepId); Assert.AreEqual("1.1", model[1].StepId); @@ -151,21 +151,42 @@ public void Registrations_Order_with_afters_only() Assert.AreEqual("3", model[5].StepId); } + [Test] + public void Should_throw_if_behavior_wants_to_go_before_connector() + { + coordinator.Register("connector", typeof(FakeStageConnector), "Connector"); + + coordinator.Register(new MyCustomRegistration("x","connector","")); + + + Assert.Throws(() => coordinator.BuildPipelineModelFor()); + } + + [Test] + public void Should_throw_if_behavior_wants_to_go_after_connector() + { + coordinator.Register("connector", typeof(FakeStageConnector), "Connector"); + + coordinator.Register(new MyCustomRegistration("x", "", "connector")); + + + Assert.Throws(() => coordinator.BuildPipelineModelFor()); + } + class MyCustomRegistration : RegisterStep { public MyCustomRegistration(string pipelineStep, string before, string after) : base(pipelineStep, typeof(FakeBehavior), pipelineStep) { - if (!String.IsNullOrEmpty(before)) + if (!string.IsNullOrEmpty(before)) { foreach (var b in before.Split(',')) { InsertBefore(b); - } } - if (!String.IsNullOrEmpty(after)) + if (!string.IsNullOrEmpty(after)) { foreach (var a in after.Split(',')) { @@ -175,20 +196,41 @@ public MyCustomRegistration(string pipelineStep, string before, string after) } } } - class FakeBehavior:IBehavior + + class FakeBehavior: IBehavior + { + public Task Invoke(IRootContext context, Func next) + { + throw new NotImplementedException(); + } + } + + + class ReplacedBehavior : IBehavior { - public void Invoke(IncomingContext context, Action next) + public Task Invoke(IRootContext context, Func next) { throw new NotImplementedException(); } } - class ReplacedBehavior : IBehavior + class FakeStageConnector : StageConnector { - public void Invoke(IncomingContext context, Action next) + public override Task Invoke(IRootContext context, Func stage) { throw new NotImplementedException(); } } + + interface IRootContext : IBehaviorContext { } + + interface IChildContext : IIncomingContext { } + + class ChildContext : IncomingContext, IChildContext + { + public ChildContext() : base("messageId", "replyToAddress", new Dictionary(), null) + { + } + } } } diff --git a/src/NServiceBus.Core.Tests/Pipeline/BehaviorTypeCheckerTests.cs b/src/NServiceBus.Core.Tests/Pipeline/BehaviorTypeCheckerTests.cs index 9728bbf1a3a..f47476c2cbd 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/BehaviorTypeCheckerTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/BehaviorTypeCheckerTests.cs @@ -1,62 +1,138 @@ namespace NServiceBus.Core.Tests.Pipeline { using System; + using System.Threading.Tasks; using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; using NUnit.Framework; [TestFixture] public class BehaviorTypeCheckerTests { + const string Description = "foo"; + [Test] public void Should_not_throw_for_simple_behavior() { - BehaviorTypeChecker.ThrowIfInvalid(typeof(ValidBehavior), "foo"); + BehaviorTypeChecker.ThrowIfInvalid(typeof(ValidBehavior), Description); } - class ValidBehavior : IBehavior + [Test] + public void Should_not_throw_for_behavior_using_context_interfaces() { - public void Invoke(RootContext context, Action next) - { - } + BehaviorTypeChecker.ThrowIfInvalid(typeof(BehaviorUsingContextInterface), Description); } [Test] public void Should_not_throw_for_closed_generic_behavior() { - BehaviorTypeChecker.ThrowIfInvalid(typeof(GenericBehavior), "foo"); + BehaviorTypeChecker.ThrowIfInvalid(typeof(GenericBehavior), Description); } [Test] public void Should_throw_for_non_behavior() { - Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(string), "foo")); + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(string), Description)); } [Test] public void Should_throw_for_open_generic_behavior() { - Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(GenericBehavior<>), "foo")); + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(GenericBehavior<>), Description)); + } + + [Test] + public void Should_throw_for_abstract_behavior() + { + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(AbstractBehavior), Description)); } - class GenericBehavior : IBehavior + [Test] + public void Should_throw_for_behavior_using_IIncomingContext() + { + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(BehaviorUsingIncomingContext), Description)); + } + + [Test] + public void Should_throw_for_behavior_using_IOutgoingContext() { - public void Invoke(RootContext context, Action next) + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(BehaviorUsingOutgoingContext), Description)); + } + + [Test] + public void Should_throw_for_behavior_using_IBehaviorContext() + { + Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(BehaviorUsingBehaviorContext), Description)); + } + + interface IRootContext : IBehaviorContext { } + + class ValidBehavior : IBehavior + { + public Task Invoke(IRootContext context, Func next) { + return TaskEx.CompletedTask; } } - [Test] - public void Should_throw_for_abstract_behavior() + class BehaviorUsingBehaviorContext : IBehavior + { + public Task Invoke(IBehaviorContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + class BehaviorUsingIncomingContext : IBehavior + { + public Task Invoke(IIncomingContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + class BehaviorUsingOutgoingContext : IBehavior { - Assert.Throws(() => BehaviorTypeChecker.ThrowIfInvalid(typeof(AbstractBehavior), "foo")); + public Task Invoke(IOutgoingContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + class BehaviorUsingContextImplementationOnTTo : IBehavior + { + public Task Invoke(IAuditContext context, Func stage) + { + return TaskEx.CompletedTask; + } } - abstract class AbstractBehavior : IBehavior + class BehaviorUsingContextInterface : IBehavior { - public void Invoke(RootContext context, Action next) + public Task Invoke(IAuditContext context, Func next) { + return TaskEx.CompletedTask; } } + + class BehaviorUsingContextImplementationOnTFrom : IBehavior + { + public Task Invoke(RootContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + class GenericBehavior : IBehavior + { + public Task Invoke(IRootContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + abstract class AbstractBehavior : IBehavior + { + public abstract Task Invoke(IRootContext context, Func next); + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/HandlerTransactionScopeWrapperBehaviorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/HandlerTransactionScopeWrapperBehaviorTests.cs new file mode 100644 index 00000000000..04a7d550d9c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/HandlerTransactionScopeWrapperBehaviorTests.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Core.Tests.Pipeline +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using NUnit.Framework; + + [TestFixture] + public class HandlerTransactionScopeWrapperBehaviorTests + { + [Test] + public void ShouldBlowUpIfExistingScopeExists() + { + var behavior = new TransactionScopeUnitOfWorkBehavior(new TransactionOptions()); + + Assert.That(async () => + { + using (new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled)) + { + await behavior.Invoke(null, ctx => TaskEx.CompletedTask); + } + }, Throws.InstanceOf().And.Message.Contains("Ambient transaction detected. The transaction scope unit of work is not supported when there already is a scope present.")); + } + + [Test] + public Task ShouldWrapInnerBehaviorsIfNoAmbientExists() + { + var behavior = new TransactionScopeUnitOfWorkBehavior(new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }); + + return behavior.Invoke(null, ctx => + { + Assert.NotNull(Transaction.Current); + return TaskEx.CompletedTask; + }); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/HeaderOptionExtensionsTests.cs b/src/NServiceBus.Core.Tests/Pipeline/HeaderOptionExtensionsTests.cs new file mode 100644 index 00000000000..9f416191764 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/HeaderOptionExtensionsTests.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.Core.Tests.Pipeline +{ + using NUnit.Framework; + + [TestFixture] + public class HeaderOptionExtensionsTests + { + [Test] + public void GetHeaders_Should_Return_Configured_Headers() + { + var options = new SendOptions(); + options.SetHeader("custom header key 1", "custom header value 1"); + options.SetHeader("custom header key 2", "custom header value 2"); + + var result = options.GetHeaders(); + + Assert.AreEqual(3, result.Count); + CollectionAssert.Contains(result.Values, "custom header value 1"); + CollectionAssert.Contains(result.Values, "custom header value 2"); + CollectionAssert.Contains(result.Keys, Headers.MessageId); + } + + [Test] + public void GetHeaders_Should_Return_Collection_With_MessageId_Header_Configured() + { + var options = new PublishOptions(); + + var result = options.GetHeaders(); + + Assert.AreEqual(1, result.Count); + CollectionAssert.Contains(result.Keys, Headers.MessageId); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs new file mode 100644 index 00000000000..1286695a31f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -0,0 +1,201 @@ +namespace NServiceBus.Core.Tests.Pipeline.Incoming +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Transactions; + using NServiceBus.Pipeline; + using NServiceBus.Sagas; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class InvokeHandlerTerminatorTest + { + [Test] + public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() + { + var handlerInvoked = false; + var terminator = new InvokeHandlerTerminator(); + var saga = new FakeSaga(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => handlerInvoked = true, saga); + var behaviorContext = CreateBehaviorContext(messageHandler); + AssociateSagaWithMessage(saga, behaviorContext); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.IsTrue(handlerInvoked); + } + + [Test] + public async Task When_saga_not_found_and_handler_is_saga_should_not_invoke_handler() + { + var handlerInvoked = false; + var terminator = new InvokeHandlerTerminator(); + var saga = new FakeSaga(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => handlerInvoked = true, saga); + var behaviorContext = CreateBehaviorContext(messageHandler); + var sagaInstance = AssociateSagaWithMessage(saga, behaviorContext); + sagaInstance.MarkAsNotFound(); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.IsFalse(handlerInvoked); + } + + [Test] + public async Task When_saga_not_found_and_handler_is_not_saga_should_invoke_handler() + { + var handlerInvoked = false; + var terminator = new InvokeHandlerTerminator(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => handlerInvoked = true, new FakeMessageHandler()); + var behaviorContext = CreateBehaviorContext(messageHandler); + var sagaInstance = AssociateSagaWithMessage(new FakeSaga(), behaviorContext); + sagaInstance.MarkAsNotFound(); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.IsTrue(handlerInvoked); + } + + [Test] + public async Task When_no_saga_should_invoke_handler() + { + var handlerInvoked = false; + var terminator = new InvokeHandlerTerminator(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => handlerInvoked = true, new FakeMessageHandler()); + var behaviorContext = CreateBehaviorContext(messageHandler); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.IsTrue(handlerInvoked); + } + + [Test] + public async Task Should_invoke_handler_with_current_message() + { + object receivedMessage = null; + var terminator = new InvokeHandlerTerminator(); + var messageHandler = CreateMessageHandler((i, m, ctx) => receivedMessage = m, new FakeMessageHandler()); + var behaviorContext = CreateBehaviorContext(messageHandler); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.AreSame(behaviorContext.MessageBeingHandled, receivedMessage); + } + + [Test] + public async Task Should_indicate_when_no_transaction_scope_is_present() + { + var terminator = new InvokeHandlerTerminator(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => { }, new FakeMessageHandler()); + var behaviorContext = CreateBehaviorContext(messageHandler); + + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + + Assert.IsFalse(behaviorContext.Extensions.Get().ScopeWasPresent); + } + + [Test] + public async Task Should_indicate_when_transaction_scope_is_present() + { + var terminator = new InvokeHandlerTerminator(); + + var messageHandler = CreateMessageHandler((i, m, ctx) => { }, new FakeMessageHandler()); + var behaviorContext = CreateBehaviorContext(messageHandler); + + using (var scope = new TransactionScope()) + { + await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask); + scope.Complete(); + } + + Assert.IsTrue(behaviorContext.Extensions.Get().ScopeWasPresent); + } + + [Test] + public void Should_throw_friendly_exception_if_handler_returns_null() + { + var terminator = new InvokeHandlerTerminator(); + var messageHandler = CreateMessageHandlerThatReturnsNull((i, m, ctx) => { }, new FakeSaga()); + var behaviorContext = CreateBehaviorContext(messageHandler); + + Assert.That(async () => await terminator.Invoke(behaviorContext, _ => TaskEx.CompletedTask), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + static ActiveSagaInstance AssociateSagaWithMessage(FakeSaga saga, IInvokeHandlerContext behaviorContext) + { + var sagaInstance = new ActiveSagaInstance(saga, SagaMetadata.Create(typeof(FakeSaga), new List(), new Conventions()), () => DateTime.UtcNow); + behaviorContext.Extensions.Set(sagaInstance); + return sagaInstance; + } + + static MessageHandler CreateMessageHandler(Action invocationAction, object handlerInstance) + { + var messageHandler = new MessageHandler((instance, message, handlerContext) => + { + invocationAction(instance, message, handlerContext); + return TaskEx.CompletedTask; + }, handlerInstance.GetType()) + { + Instance = handlerInstance + }; + return messageHandler; + } + + static MessageHandler CreateMessageHandlerThatReturnsNull(Action invocationAction, object handlerInstance) + { + var messageHandler = new MessageHandler((instance, message, handlerContext) => + { + invocationAction(instance, message, handlerContext); + return null; + }, handlerInstance.GetType()) + { + Instance = handlerInstance + }; + return messageHandler; + } + + static IInvokeHandlerContext CreateBehaviorContext(MessageHandler messageHandler) + { + var behaviorContext = new TestableInvokeHandlerContext + { + MessageHandler = messageHandler + }; + + return behaviorContext; + } + + class FakeSaga : Saga, IAmStartedByMessages + { + public Task Handle(StartMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeId).ToSaga(saga => saga.SomeId); + } + + public class FakeSagaData : ContainSagaData + { + public string SomeId { get; set; } + } + } + + class StartMessage + { + public string SomeId { get; set; } + } + + class FakeMessageHandler + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/SerializeMessageConnectorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/SerializeMessageConnectorTests.cs new file mode 100644 index 00000000000..c9f3b3514cf --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/SerializeMessageConnectorTests.cs @@ -0,0 +1,59 @@ +namespace NServiceBus.Core.Tests.Pipeline.Incoming +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Serialization; + using Testing; + using Unicast.Messages; + + [TestFixture] + public class SerializeMessageConnectorTests + { + [Test] + public async Task Should_set_content_type_header() + { + var registry = new MessageMetadataRegistry(new Conventions()); + + registry.RegisterMessageTypesFoundIn(new List + { + typeof(MyMessage) + }); + + var context = new TestableOutgoingLogicalMessageContext(); + context.Message = new OutgoingLogicalMessage(typeof(MyMessage), new MyMessage()); + + var behavior = new SerializeMessageConnector(new FakeSerializer("myContentType"), registry); + + await behavior.Invoke(context, c => TaskEx.CompletedTask); + + Assert.AreEqual("myContentType", context.Headers[Headers.ContentType]); + } + + class FakeSerializer : IMessageSerializer + { + public FakeSerializer(string contentType) + { + ContentType = contentType; + } + + public void Serialize(object message, Stream stream) + { + } + + public object[] Deserialize(Stream stream, IList messageTypes = null) + { + throw new NotImplementedException(); + } + + public string ContentType { get; } + } + + class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehaviorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehaviorTests.cs new file mode 100644 index 00000000000..0b9cddb6e2e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehaviorTests.cs @@ -0,0 +1,113 @@ +namespace NServiceBus.Core.Tests.Pipeline.MutateInstanceMessage +{ + using System.Threading.Tasks; + using MessageMutator; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Testing; + using Unicast.Messages; + + [TestFixture] + class MutateIncomingMessageBehaviorTests + { + [Test] + public void Should_throw_friendly_exception_when_IMutateIncomingMessages_MutateIncoming_returns_null() + { + var behavior = new MutateIncomingMessageBehavior(); + + var logicalMessage = new LogicalMessage(new MessageMetadata(typeof(TestMessage)), new TestMessage()); + + var context = new TestableIncomingLogicalMessageContext + { + Message = logicalMessage + }; + + context.Builder.Register(() => new MutateIncomingMessagesReturnsNull()); + + Assert.That(async () => await behavior.Invoke(context, ctx => TaskEx.CompletedTask), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + [Test] + public async Task When_no_mutator_updates_the_body_should_not_update_the_body() + { + var behavior = new MutateIncomingMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingLogicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichDoesNotMutateTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_no_mutator_available_should_not_update_the_body() + { + var behavior = new MutateIncomingMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingLogicalMessageContext(); + + context.Builder.Register(() => new IMutateIncomingMessages[] { }); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_mutator_modifies_the_body_should_update_the_body() + { + var behavior = new MutateIncomingMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingLogicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichMutatesTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.True(context.UpdateMessageCalled); + } + + class InterceptUpdateMessageIncomingLogicalMessageContext : TestableIncomingLogicalMessageContext + { + public bool UpdateMessageCalled { get; private set; } + + public override void UpdateMessageInstance(object newInstance) + { + base.UpdateMessageInstance(newInstance); + + UpdateMessageCalled = true; + } + } + + class MutatorWhichDoesNotMutateTheBody : IMutateIncomingMessages + { + public Task MutateIncoming(MutateIncomingMessageContext context) + { + return TaskEx.CompletedTask; + } + } + + class MutatorWhichMutatesTheBody : IMutateIncomingMessages + { + public Task MutateIncoming(MutateIncomingMessageContext context) + { + context.Message = new object(); + + return TaskEx.CompletedTask; + } + } + + class MutateIncomingMessagesReturnsNull : IMutateIncomingMessages + { + public Task MutateIncoming(MutateIncomingMessageContext context) + { + return null; + } + } + + class TestMessage : IMessage + { } + } +} diff --git a/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehaviorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehaviorTests.cs new file mode 100644 index 00000000000..7e365e0b61a --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehaviorTests.cs @@ -0,0 +1,107 @@ +namespace NServiceBus.Core.Tests.Pipeline.MutateInstanceMessage +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using MessageMutator; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Testing; + using Transport; + + [TestFixture] + class MutateOutgoingMessageBehaviorTests + { + [Test] + public void Should_throw_friendly_exception_when_IMutateOutgoingMessages_MutateOutgoing_returns_null() + { + var behavior = new MutateOutgoingMessageBehavior(); + + var context = new TestableOutgoingLogicalMessageContext(); + context.Extensions.Set(new IncomingMessage("messageId", new Dictionary(), new byte[0])); + context.Extensions.Set(new LogicalMessage(null, null)); + context.Builder.Register(() => new MutateOutgoingMessagesReturnsNull()); + + Assert.That(async () => await behavior.Invoke(context, ctx => TaskEx.CompletedTask), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + [Test] + public async Task When_no_mutator_updates_the_body_should_not_update_the_body() + { + var behavior = new MutateOutgoingMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingLogicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichDoesNotMutateTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_no_mutator_available_should_not_update_the_body() + { + var behavior = new MutateOutgoingMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingLogicalMessageContext(); + + context.Builder.Register(() => new IMutateOutgoingMessages[] { }); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_mutator_modifies_the_body_should_update_the_body() + { + var behavior = new MutateOutgoingMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingLogicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichMutatesTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.True(context.UpdateMessageCalled); + } + + class InterceptUpdateMessageOutgoingLogicalMessageContext : TestableOutgoingLogicalMessageContext + { + public bool UpdateMessageCalled { get; private set; } + + public override void UpdateMessage(object newInstance) + { + base.UpdateMessage(newInstance); + + UpdateMessageCalled = true; + } + } + + class MutateOutgoingMessagesReturnsNull : IMutateOutgoingMessages + { + public Task MutateOutgoing(MutateOutgoingMessageContext context) + { + return null; + } + } + + class MutatorWhichDoesNotMutateTheBody : IMutateOutgoingMessages + { + public Task MutateOutgoing(MutateOutgoingMessageContext context) + { + return TaskEx.CompletedTask; + } + } + + class MutatorWhichMutatesTheBody : IMutateOutgoingMessages + { + public Task MutateOutgoing(MutateOutgoingMessageContext context) + { + context.OutgoingMessage = new object(); + + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehaviorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehaviorTests.cs new file mode 100644 index 00000000000..1cdc6fddde5 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehaviorTests.cs @@ -0,0 +1,103 @@ +namespace NServiceBus.Core.Tests.Pipeline.MutateTransportMessage +{ + using System.Threading.Tasks; + using MessageMutator; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class MutateIncomingTransportMessageBehaviorTests + { + [Test] + public void Should_throw_friendly_exception_when_IMutateIncomingTransportMessages_MutateIncoming_returns_null() + { + var behavior = new MutateIncomingTransportMessageBehavior(); + + var context = new TestableIncomingPhysicalMessageContext(); + + context.Builder.Register(() => new MutateIncomingTransportMessagesReturnsNull()); + + Assert.That(async () => await behavior.Invoke(context, ctx => TaskEx.CompletedTask), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + [Test] + public async Task When_no_mutator_updates_the_body_should_not_update_the_body() + { + var behavior = new MutateIncomingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingPhysicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichDoesNotMutateTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageBodyCalled); + } + + [Test] + public async Task When_no_mutator_available_should_not_update_the_body() + { + var behavior = new MutateIncomingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingPhysicalMessageContext(); + + context.Builder.Register(() => new IMutateIncomingTransportMessages[]{ }); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageBodyCalled); + } + + [Test] + public async Task When_mutator_modifies_the_body_should_update_the_body() + { + var behavior = new MutateIncomingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageIncomingPhysicalMessageContext(); + + context.Builder.Register(() => new MutatorWhichMutatesTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.True(context.UpdateMessageBodyCalled); + } + + class InterceptUpdateMessageIncomingPhysicalMessageContext : TestableIncomingPhysicalMessageContext + { + public bool UpdateMessageBodyCalled { get; private set; } + + public override void UpdateMessage(byte[] body) + { + base.UpdateMessage(body); + + UpdateMessageBodyCalled = true; + } + } + + class MutateIncomingTransportMessagesReturnsNull : IMutateIncomingTransportMessages + { + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + return null; + } + } + + class MutatorWhichDoesNotMutateTheBody : IMutateIncomingTransportMessages + { + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + return TaskEx.CompletedTask; + } + } + + class MutatorWhichMutatesTheBody : IMutateIncomingTransportMessages + { + public Task MutateIncoming(MutateIncomingTransportMessageContext context) + { + context.Body = new byte[0]; + + return TaskEx.CompletedTask; + } + } + } +} diff --git a/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehaviorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehaviorTests.cs new file mode 100644 index 00000000000..f57214d631f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehaviorTests.cs @@ -0,0 +1,119 @@ +namespace NServiceBus.Core.Tests.Pipeline.MutateTransportMessage +{ + using System.Threading.Tasks; + using MessageMutator; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Testing; + + [TestFixture] + class MutateOutgoingTransportMessageBehaviorTests + { + [Test] + public void Should_throw_friendly_exception_when_IMutateOutgoingTransportMessages_MutateOutgoing_returns_null() + { + var behavior = new MutateOutgoingTransportMessageBehavior(); + + var physicalContext = new TestableOutgoingPhysicalMessageContext(); + physicalContext.Extensions.Set(new OutgoingLogicalMessage(typeof(FakeMessage), new FakeMessage())); + physicalContext.Builder.Register(() => new MutateOutgoingTransportMessagesReturnsNull()); + + Assert.That(async () => await behavior.Invoke(physicalContext, ctx => TaskEx.CompletedTask), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + [Test] + public async Task When_no_mutator_updates_the_body_should_not_update_the_body() + { + var behavior = new MutateOutgoingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingPhysicalMessageContext(); + context.Extensions.Set(new OutgoingLogicalMessage(typeof(FakeMessage), new FakeMessage())); + + context.Builder.Register(() => new MutatorWhichDoesNotMutateTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_no_mutator_available_should_not_update_the_body() + { + var behavior = new MutateOutgoingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingPhysicalMessageContext(); + context.Extensions.Set(new OutgoingLogicalMessage(typeof(FakeMessage), new FakeMessage())); + + context.Builder.Register(() => new IMutateOutgoingTransportMessages[] { }); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.False(context.UpdateMessageCalled); + } + + [Test] + public async Task When_mutator_modifies_the_body_should_update_the_body() + { + var behavior = new MutateOutgoingTransportMessageBehavior(); + + var context = new InterceptUpdateMessageOutgoingPhysicalMessageContext(); + context.Extensions.Set(new OutgoingLogicalMessage(typeof(FakeMessage), new FakeMessage())); + + context.Builder.Register(() => new MutatorWhichMutatesTheBody()); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.True(context.UpdateMessageCalled); + } + + class InterceptUpdateMessageOutgoingPhysicalMessageContext : TestableOutgoingPhysicalMessageContext + { + public bool UpdateMessageCalled { get; private set; } + + public override void UpdateMessage(byte[] body) + { + base.UpdateMessage(body); + + UpdateMessageCalled = true; + } + } + + class MutateOutgoingMessagesReturnsNull : IMutateOutgoingTransportMessages + { + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + return null; + } + } + + class MutatorWhichDoesNotMutateTheBody : IMutateOutgoingTransportMessages + { + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + return TaskEx.CompletedTask; + } + } + + class MutatorWhichMutatesTheBody : IMutateOutgoingTransportMessages + { + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + context.OutgoingBody = new byte[0]; + + return TaskEx.CompletedTask; + } + } + + class MutateOutgoingTransportMessagesReturnsNull : IMutateOutgoingTransportMessages + { + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + return null; + } + } + + class FakeMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageTests.cs new file mode 100644 index 00000000000..abd18a533ba --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageTests.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using System; + using System.Collections.Generic; + using NServiceBus.Routing; + using Transport; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class AttachSenderRelatedInfoOnMessageTests + { + [Test] + public void Should_set_the_time_sent_header() + { + var message = InvokeBehavior(); + + Assert.True(message.Headers.ContainsKey(Headers.TimeSent)); + } + + [Test] + public void Should_not_override_the_time_sent_header() + { + var timeSent = DateTime.UtcNow.ToString(); + + var message = InvokeBehavior(new Dictionary + { + {Headers.TimeSent, timeSent} + }); + + Assert.True(message.Headers.ContainsKey(Headers.TimeSent)); + Assert.AreEqual(timeSent, message.Headers[Headers.TimeSent]); + } + + + [Test] + public void Should_set_the_nsb_version_header() + { + var message = InvokeBehavior(); + + Assert.True(message.Headers.ContainsKey(Headers.NServiceBusVersion)); + } + + [Test] + public void Should_not_override_nsb_version_header() + { + var nsbVersion = "some-crazy-version-number"; + var message = InvokeBehavior(new Dictionary + { + {Headers.NServiceBusVersion, nsbVersion} + }); + + Assert.True(message.Headers.ContainsKey(Headers.NServiceBusVersion)); + Assert.AreEqual(nsbVersion, message.Headers[Headers.NServiceBusVersion]); + } + + static OutgoingMessage InvokeBehavior(Dictionary headers = null) + { + var message = new OutgoingMessage("id", headers ?? new Dictionary(), null); + + new AttachSenderRelatedInfoOnMessageBehavior() + .Invoke(new TestableRoutingContext {Message = message, RoutingStrategies = new List { new UnicastRoutingStrategy("_") }}, _ => TaskEx.CompletedTask); + + return message; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/ImmediateDispatchOptionExtensionsTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/ImmediateDispatchOptionExtensionsTests.cs new file mode 100644 index 00000000000..e1416845cbf --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/ImmediateDispatchOptionExtensionsTests.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using NUnit.Framework; + + [TestFixture] + public class ImmediateDispatchOptionExtensionsTests + { + [Test] + public void RequiresImmediateDispatch_Should_Return_False_When_No_Immediate_Dispatch_Requested() + { + var options = new SendOptions(); + + Assert.IsFalse(options.RequiredImmediateDispatch()); + } + + [Test] + public void RequiresImmediateDispatch_Should_Return_True_When_Immediate_Dispatch_Requested() + { + var options = new SendOptions(); + options.RequireImmediateDispatch(); + + Assert.IsTrue(options.RequiredImmediateDispatch()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/MessageIdExtensionsTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/MessageIdExtensionsTests.cs new file mode 100644 index 00000000000..a786c39179e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/MessageIdExtensionsTests.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using NUnit.Framework; + + [TestFixture] + public class MessageIdExtensionsTests + { + [Test] + public void GetMessageId_Should_Return_Generated_Id_When_No_Id_Specified() + { + var options = new SendOptions(); + + Assert.IsNotEmpty(options.GetMessageId()); + } + + [Test] + public void GetMessageId_Should_Return_Defined_Id() + { + const string expectedMessageID = "expected message id"; + var options = new PublishOptions(); + options.SetMessageId(expectedMessageID); + + Assert.AreEqual(expectedMessageID, options.GetMessageId()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingPublishContextTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingPublishContextTests.cs new file mode 100644 index 00000000000..8e73a0453a3 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingPublishContextTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using NServiceBus.Pipeline; + using NUnit.Framework; + + [TestFixture] + public class OutgoingPublishContextTests + { + [Test] + public void ShouldShallowCloneHeaders() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new PublishOptions(); + options.SetHeader("someHeader", "someValue"); + + var testee = new OutgoingPublishContext(message, options, new RootContext(null, null, null)); + testee.Headers["someHeader"] = "updatedValue"; + testee.Headers["anotherHeader"] = "anotherValue"; + + Assert.AreEqual("someValue", options.OutgoingHeaders["someHeader"]); + Assert.IsFalse(options.OutgoingHeaders.ContainsKey("anotherHeader")); + Assert.AreEqual("updatedValue", testee.Headers["someHeader"]); + Assert.AreEqual("anotherValue", testee.Headers["anotherHeader"]); + } + + [Test] + public void ShouldShallowCloneContext() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new PublishOptions(); + options.Context.Set("someKey", "someValue"); + + var testee = new OutgoingPublishContext(message, options, new RootContext(null, null, null)); + testee.Extensions.Set("someKey", "updatedValue"); + testee.Extensions.Set("anotherKey", "anotherValue"); + + string value; + string anotherValue; + options.Context.TryGet("someKey", out value); + Assert.AreEqual("someValue", value); + Assert.IsFalse(options.Context.TryGet("anotherKey", out anotherValue)); + string updatedValue; + string anotherValue2; + testee.Extensions.TryGet("someKey", out updatedValue); + testee.Extensions.TryGet("anotherKey", out anotherValue2); + Assert.AreEqual("updatedValue", updatedValue); + Assert.AreEqual("anotherValue", anotherValue2); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingReplyContextTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingReplyContextTests.cs new file mode 100644 index 00000000000..145e018564a --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingReplyContextTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using NServiceBus.Pipeline; + using NUnit.Framework; + + [TestFixture] + public class OutgoingReplyContextTests + { + [Test] + public void ShouldShallowCloneHeaders() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new ReplyOptions(); + options.SetHeader("someHeader", "someValue"); + + var testee = new OutgoingReplyContext(message, options, new RootContext(null, null, null)); + testee.Headers["someHeader"] = "updatedValue"; + testee.Headers["anotherHeader"] = "anotherValue"; + + Assert.AreEqual("someValue", options.OutgoingHeaders["someHeader"]); + Assert.IsFalse(options.OutgoingHeaders.ContainsKey("anotherHeader")); + Assert.AreEqual("updatedValue", testee.Headers["someHeader"]); + Assert.AreEqual("anotherValue", testee.Headers["anotherHeader"]); + } + + [Test] + public void ShouldShallowCloneContext() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new ReplyOptions(); + options.Context.Set("someKey", "someValue"); + + var testee = new OutgoingReplyContext(message, options, new RootContext(null, null, null)); + testee.Extensions.Set("someKey", "updatedValue"); + testee.Extensions.Set("anotherKey", "anotherValue"); + + string value; + string anotherValue; + options.Context.TryGet("someKey", out value); + Assert.AreEqual("someValue", value); + Assert.IsFalse(options.Context.TryGet("anotherKey", out anotherValue)); + string updatedValue; + string anotherValue2; + testee.Extensions.TryGet("someKey", out updatedValue); + testee.Extensions.TryGet("anotherKey", out anotherValue2); + Assert.AreEqual("updatedValue", updatedValue); + Assert.AreEqual("anotherValue", anotherValue2); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingSendContextTests.cs b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingSendContextTests.cs new file mode 100644 index 00000000000..7149f417148 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/Outgoing/OutgoingSendContextTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Pipeline.Outgoing +{ + using NServiceBus.Pipeline; + using NUnit.Framework; + + [TestFixture] + public class OutgoingSendContextTests + { + [Test] + public void ShouldShallowCloneHeaders() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new SendOptions(); + options.SetHeader("someHeader", "someValue"); + + var testee = new OutgoingSendContext(message, options, new RootContext(null, null, null)); + testee.Headers["someHeader"] = "updatedValue"; + testee.Headers["anotherHeader"] = "anotherValue"; + + Assert.AreEqual("someValue", options.OutgoingHeaders["someHeader"]); + Assert.IsFalse(options.OutgoingHeaders.ContainsKey("anotherHeader")); + Assert.AreEqual("updatedValue", testee.Headers["someHeader"]); + Assert.AreEqual("anotherValue", testee.Headers["anotherHeader"]); + } + + [Test] + public void ShouldShallowCloneContext() + { + var message = new OutgoingLogicalMessage(typeof(object), new object()); + var options = new SendOptions(); + options.Context.Set("someKey", "someValue"); + + var testee = new OutgoingSendContext(message, options, new RootContext(null, null, null)); + testee.Extensions.Set("someKey", "updatedValue"); + testee.Extensions.Set("anotherKey", "anotherValue"); + + string value; + string anotherValue; + options.Context.TryGet("someKey", out value); + Assert.AreEqual("someValue", value); + Assert.IsFalse(options.Context.TryGet("anotherKey", out anotherValue)); + string updatedValue; + string anotherValue2; + testee.Extensions.TryGet("someKey", out updatedValue); + testee.Extensions.TryGet("anotherKey", out anotherValue2); + Assert.AreEqual("updatedValue", updatedValue); + Assert.AreEqual("anotherValue", anotherValue2); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineModelBuilderTests.cs b/src/NServiceBus.Core.Tests/Pipeline/PipelineModelBuilderTests.cs new file mode 100644 index 00000000000..4915b9c4549 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/PipelineModelBuilderTests.cs @@ -0,0 +1,324 @@ +namespace NServiceBus.Core.Tests.Pipeline +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class PipelineModelBuilderTests + { + [Test] + public void ShouldDetectConflictingStepRegistrations() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root1", typeof(RootBehavior), "desc"), + RegisterStep.Create("Root1", typeof(ChildBehaviorOfChildContextNotInheritedFromParentContext), "desc"), + + }, new List(), new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("Step registration with id 'Root1' is already registered for 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+RootBehavior'.", ex.Message); + } + + [Test] + public void ShouldOnlyAllowReplacementOfExistingRegistrations() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root1", typeof(RootBehavior), "desc"), + + }, new List(), new List + { + new ReplaceStep("DoesNotExist", typeof(RootBehavior), "desc"), + }); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("You can only replace an existing step registration, 'DoesNotExist' registration does not exist.", ex.Message); + } + + [Test] + public void ShouldOnlyAllowRemovalOfExistingRegistrations() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root1", typeof(RootBehavior), "desc"), + + }, new List + { + new RemoveStep("DoesNotExist"), + }, new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("You cannot remove step registration with id 'DoesNotExist', registration does not exist.", ex.Message); + } + + [Test] + public void ShouldOnlyAllowRemovalWhenNoOtherDependsOnItsBeforeRegistration() + { + var someBehaviorRegistration = RegisterStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"); + var anotherBehaviorRegistration = RegisterStep.Create("AnotherBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"); + + anotherBehaviorRegistration.InsertBefore("SomeBehaviorOfParentContext"); + + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + someBehaviorRegistration, + anotherBehaviorRegistration, + + }, new List { new RemoveStep("SomeBehaviorOfParentContext")}, new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("You cannot remove step registration with id 'SomeBehaviorOfParentContext', registration with id 'AnotherBehaviorOfParentContext' depends on it.", ex.Message); + } + + [Test] + public void ShouldOnlyAllowRemovalWhenNoOtherDependsOnItsAfterRegistration() + { + var someBehaviorRegistration = RegisterStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"); + var anotherBehaviorRegistration = RegisterStep.Create("AnotherBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"); + + anotherBehaviorRegistration.InsertAfter("SomeBehaviorOfParentContext"); + + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + someBehaviorRegistration, + anotherBehaviorRegistration, + + }, new List { new RemoveStep("SomeBehaviorOfParentContext") }, new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("You cannot remove step registration with id 'SomeBehaviorOfParentContext', registration with id 'AnotherBehaviorOfParentContext' depends on it.", ex.Message); + } + + [Test] + public void ShouldDetectMissingBehaviorForRootContext() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Child", typeof(ChildBehaviorOfChildContext), "desc"), + + }, new List(), new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("Can't find any behaviors/connectors for the root context (NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+IParentContext)", ex.Message); + } + + [Test] + public void ShouldDetectConflictingStageConnectors() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root1", typeof(RootBehavior), "desc"), + RegisterStep.Create("ParentContextToChildContextConnector", typeof(ParentContextToChildContextConnector), "desc"), + RegisterStep.Create("ParentContextToChildContextNotInheritedFromParentContextConnector", typeof(ParentContextToChildContextNotInheritedFromParentContextConnector), "desc") + + }, new List(), new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("Multiple stage connectors found for stage 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+IParentContext'. Remove one of: 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+ParentContextToChildContextConnector', 'NServiceBus.Core.Tests.Pipeline.PipelineModelBuilderTests+ParentContextToChildContextNotInheritedFromParentContextConnector'", ex.Message); + } + + [Test] + public void ShouldDetectNonExistingInsertAfterRegistrations() + { + var someBehaviorRegistration = RegisterStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"); + var anotherBehaviorRegistration = RegisterStep.Create("AnotherBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"); + + anotherBehaviorRegistration.InsertAfter("DoesNotExist"); + + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + someBehaviorRegistration, + anotherBehaviorRegistration, + + }, new List(), new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("Registration 'DoesNotExist' specified in the insertafter of the 'AnotherBehaviorOfParentContext' step does not exist. Current StepIds: 'SomeBehaviorOfParentContext', 'AnotherBehaviorOfParentContext'", ex.Message); + } + + [Test] + public void ShouldDetectNonExistingInsertBeforeRegistrations() + { + var someBehaviorRegistration = RegisterStep.Create("SomeBehaviorOfParentContext", typeof(SomeBehaviorOfParentContext), "desc"); + var anotherBehaviorRegistration = RegisterStep.Create("AnotherBehaviorOfParentContext", typeof(AnotherBehaviorOfParentContext), "desc"); + + anotherBehaviorRegistration.InsertBefore("DoesNotExist"); + + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + someBehaviorRegistration, + anotherBehaviorRegistration, + + }, new List(), new List()); + + + var ex = Assert.Throws(() => builder.Build()); + + Assert.AreEqual("Registration 'DoesNotExist' specified in the insertbefore of the 'AnotherBehaviorOfParentContext' step does not exist. Current StepIds: 'SomeBehaviorOfParentContext', 'AnotherBehaviorOfParentContext'", ex.Message); + } + + [Test] + public void ShouldDetectRegistrationsWithContextsReachableFromTheRootContext() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root", typeof(RootBehavior), "desc"), + RegisterStep.Create("ParentContextToChildContextNotInheritedFromParentContextConnector", typeof(ParentContextToChildContextNotInheritedFromParentContextConnector), "desc"), + RegisterStep.Create("Child", typeof(ChildBehaviorOfChildContextNotInheritedFromParentContext), "desc") + }, new List(), new List()); + + + var model = builder.Build(); + + Assert.AreEqual(3, model.Count); + } + + [Test] + public void ShouldDetectRegistrationsWithContextsNotReachableFromTheRootContext() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root", typeof(RootBehavior), "desc"), + RegisterStep.Create("ParentContextToChildContextConnector", typeof(ParentContextToChildContextConnector), "desc"), + RegisterStep.Create("Child", typeof(ChildBehaviorOfChildContextNotInheritedFromParentContext), "desc") + }, new List(), new List()); + + + var model = builder.Build(); + + Assert.AreEqual(2, model.Count); + } + + [Test] + public void ShouldHandleTheTerminator() + { + var builder = new PipelineModelBuilder(typeof(IParentContext), new List + { + RegisterStep.Create("Root1", typeof(RootBehavior), "desc"), + RegisterStep.Create("ParentContextToChildContextConnector", typeof(ParentContextToChildContextConnector), "desc"), + RegisterStep.Create("Child", typeof(ChildBehaviorOfChildContextNotInheritedFromParentContext), "desc"), + RegisterStep.Create("Terminator", typeof(Terminator), "desc") + + }, new List(), new List()); + + + var model = builder.Build(); + + Assert.AreEqual(3, model.Count); + } + + interface IParentContext : IBehaviorContext { } + + class ParentContext : BehaviorContext, IParentContext + { + public ParentContext(IBehaviorContext parentContext) + : base(parentContext) + { + } + } + + interface IChildContext : IParentContext { } + + class ChildContext : ParentContext, IChildContext + { + public ChildContext(IBehaviorContext parentContext) + : base(parentContext) + { + } + } + + interface IChildContextNotInheritedFromParentContext : IBehaviorContext { } + + class ChildContextNotInheritedFromParentContext : BehaviorContext + { + public ChildContextNotInheritedFromParentContext(IBehaviorContext parentContext) + : base(parentContext) + { + } + } + + class ParentContextToChildContextConnector : StageConnector + { + public override Task Invoke(IParentContext context, Func stage) + { + throw new NotImplementedException(); + } + } + + class Terminator : PipelineTerminator + { + protected override Task Terminate(IChildContext context) + { + throw new NotImplementedException(); + } + } + + class ParentContextToChildContextNotInheritedFromParentContextConnector : StageConnector + { + public override Task Invoke(IParentContext context, Func stage) + { + throw new NotImplementedException(); + } + } + + class SomeBehaviorOfParentContext : IBehavior + { + public Task Invoke(IParentContext context, Func next) + { + throw new NotImplementedException(); + } + } + + class AnotherBehaviorOfParentContext : IBehavior + { + public Task Invoke(IParentContext context, Func next) + { + throw new NotImplementedException(); + } + } + + class RootBehavior : IBehavior + { + public Task Invoke(IParentContext context, Func next) + { + throw new NotImplementedException(); + } + } + + class ChildBehaviorOfChildContext : IBehavior + { + public Task Invoke(IChildContext context, Func next) + { + throw new NotImplementedException(); + } + } + + class ChildBehaviorOfChildContextNotInheritedFromParentContext : IBehavior + { + public Task Invoke(IChildContextNotInheritedFromParentContext context, Func next) + { + throw new NotImplementedException(); + } + } + } + +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineStepTests.cs b/src/NServiceBus.Core.Tests/Pipeline/PipelineStepTests.cs deleted file mode 100644 index 1fa606fba7c..00000000000 --- a/src/NServiceBus.Core.Tests/Pipeline/PipelineStepTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace NServiceBus.Core.Tests.Pipeline -{ - using NServiceBus.Pipeline; - using NUnit.Framework; - - [TestFixture] - public class PipelineStepTests - { - [Test] - public void Should_be_able_to_create_a_custom_pipeline_step_and_use_as_a_string() - { - const string customStepId = "custom"; - var pipelineStep = WellKnownStep.Create(customStepId); - Assert.AreEqual(customStepId, (string)pipelineStep, "couldn't convert pipeline step into a string"); - } - - [Test] - [ExpectedException] - public void Should_not_allow_empty_string_for_a_custom_pipeline_step() - { - WellKnownStep.Create(string.Empty); - } - - [Test] - [ExpectedException] - public void Should_not_allow_null_for_a_custom_pipeline_step() - { - WellKnownStep.Create(null); - } - - [Test] - public void Should_be_able_to_convert_a_built_in_pipeline_step_to_a_string() - { - var pipelineStep = WellKnownStep.AuditProcessedMessage; - Assert.AreEqual("AuditProcessedMessage", (string)pipelineStep, "couldn't convert pipeline step into a string"); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldCreateCachedExecutionPlan.approved.txt b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldCreateCachedExecutionPlan.approved.txt new file mode 100644 index 00000000000..41b8223e077 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldCreateCachedExecutionPlan.approved.txt @@ -0,0 +1,6 @@ +context0 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+StageFork).Invoke(context0, value(System.Func`2[NServiceBus.Pipeline.IIncomingPhysicalMessageContext,System.Threading.Tasks.Task])), + context1 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+Behavior1).Invoke(context1, value(System.Func`2[NServiceBus.Pipeline.IIncomingPhysicalMessageContext,System.Threading.Tasks.Task])), + context2 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+Stage1).Invoke(context2, value(System.Func`2[NServiceBus.Pipeline.IIncomingLogicalMessageContext,System.Threading.Tasks.Task])), + context3 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+Behavior2).Invoke(context3, value(System.Func`2[NServiceBus.Pipeline.IIncomingLogicalMessageContext,System.Threading.Tasks.Task])), + context4 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+Stage2).Invoke(context4, value(System.Func`2[NServiceBus.Pipeline.IDispatchContext,System.Threading.Tasks.Task])), + context5 => value(NServiceBus.Core.Tests.Pipeline.PipelineTests+Terminator).Invoke(context5, value(System.Func`2[NServiceBus.Pipeline.PipelineTerminator`1+ITerminatingContext[NServiceBus.Pipeline.IDispatchContext],System.Threading.Tasks.Task])), diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldExecutePipeline.approved.txt b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldExecutePipeline.approved.txt new file mode 100644 index 00000000000..82618e39d44 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldExecutePipeline.approved.txt @@ -0,0 +1,6 @@ +stagefork1 +behavior1 +stage1 +behavior2 +stage2 +terminator diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldNotCacheContext.approved.txt b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldNotCacheContext.approved.txt new file mode 100644 index 00000000000..bff6e938e29 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.ShouldNotCacheContext.approved.txt @@ -0,0 +1,14 @@ +Run 1 +stagefork1: 1 +behavior1: 1 +stage1: 1 +behavior2 +stage2: 1 +terminator: 1 +Run 2 +stagefork1: 2 +behavior1: 2 +stage1: 2 +behavior2 +stage2: 2 +terminator: 2 diff --git a/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.cs b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.cs new file mode 100644 index 00000000000..7976316c865 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/PipelineTests.cs @@ -0,0 +1,359 @@ +namespace NServiceBus.Core.Tests.Pipeline +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using ApprovalTests; + using Extensibility; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Settings; + using Testing; + using FakeBuilder = Testing.FakeBuilder; + + [TestFixture] + public class PipelineTests + { + [Test] + public async Task ShouldExecutePipeline() + { + var stringWriter = new StringWriter(); + + var pipelineModifications = new PipelineModifications(); + pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter)); + pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Stage2.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Terminator.Registration(stringWriter)); + + var pipeline = new Pipeline(new FakeBuilder(), new SettingsHolder(), pipelineModifications); + + var context = new TestableTransportReceiveContext(); + context.Extensions.Set(new FakePipelineCache()); + + await pipeline.Invoke(context); + + Approvals.Verify(stringWriter.ToString()); + } + + [Test] + public async Task ShouldNotCacheContext() + { + var stringWriter = new StringWriter(); + + var pipelineModifications = new PipelineModifications(); + pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter)); + pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Stage2.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Terminator.Registration(stringWriter)); + + var pipeline = new Pipeline(new FakeBuilder(), new SettingsHolder(), pipelineModifications); + + stringWriter.WriteLine("Run 1"); + + var context = new TestableTransportReceiveContext(); + context.Extensions.Set(new FakePipelineCache()); + context.Extensions.Set(ExtendableExtensions.RunSpecificKey, 1); + + await pipeline.Invoke(context); + + stringWriter.WriteLine("Run 2"); + + context = new TestableTransportReceiveContext(); + context.Extensions.Set(new FakePipelineCache()); + context.Extensions.Set(ExtendableExtensions.RunSpecificKey, 2); + + await pipeline.Invoke(context); + + Approvals.Verify(stringWriter.ToString()); + } + + [Test] + public void ShouldCreateCachedExecutionPlan() + { + var stringWriter = new StringWriter(); + + var behaviors = new IBehavior[] + { + new StageFork("stagefork1", stringWriter), + new Behavior1("behavior1", stringWriter), + new Stage1("stage1", stringWriter), + new Behavior2("behavior2", stringWriter), + new Stage2("stage2", stringWriter), + new Terminator("terminator", stringWriter), + }; + + var expressions = new List(); + behaviors.CreatePipelineExecutionExpression(expressions); + + Approvals.Verify(expressions.PrettyPrint()); + } + + [Test] + public async Task ShouldCacheExecutionFunc() + { + var stringWriter = new StringWriter(); + + var pipelineModifications = new PipelineModifications(); + pipelineModifications.Additions.Add(new Behavior1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Stage1.Registration(stringWriter)); + pipelineModifications.Additions.Add(new Behavior2.Registration(stringWriter)); + pipelineModifications.Additions.Add(new StageFork.Registration(stringWriter)); + + var pipeline = new Pipeline(new FakeBuilder(), new SettingsHolder(), pipelineModifications); + + var context = new TestableTransportReceiveContext(); + context.Extensions.Set(new FakePipelineCache()); + + var stopwatch = Stopwatch.StartNew(); + await pipeline.Invoke(context); + stopwatch.Stop(); + + var firstRunTicks = stopwatch.ElapsedTicks; + + var runs = new List(); + for (var i = 0; i < 100; i++) + { + stopwatch = Stopwatch.StartNew(); + await pipeline.Invoke(context); + stopwatch.Stop(); + runs.Add(stopwatch.ElapsedTicks); + } + + var average = runs.Average(); + + Assert.That(average, Is.LessThan(firstRunTicks / 5)); + } + + class StageFork : IStageForkConnector + { + public StageFork(string instance, TextWriter writer) + { + this.instance = instance; + this.writer = writer; + } + + public async Task Invoke(ITransportReceiveContext context, Func next) + { + context.PrintInstanceWithRunSpecificIfPossible(instance, writer); + + var physicalMessageContext = new TestableIncomingPhysicalMessageContext(); + physicalMessageContext.Extensions.Merge(context.Extensions); + + await next(physicalMessageContext).ConfigureAwait(false); + + var dispatchContext = new TestableBatchDispatchContext(); + dispatchContext.Extensions.Merge(context.Extensions); + + await this.Fork(dispatchContext).ConfigureAwait(false); + } + + + + readonly string instance; + + readonly TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("StageFork", typeof(StageFork), "StageFork", b => new StageFork("stagefork1", writer)) + { + } + } + } + + + + class Behavior1 : IBehavior + { + public Behavior1(string instance, TextWriter writer) + { + this.instance = instance; + this.writer = writer; + } + + public Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + context.PrintInstanceWithRunSpecificIfPossible(instance, writer); + return next(context); + } + + readonly string instance; + readonly TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("Behavior1", typeof(Behavior1), "Behavior1", b => new Behavior1("behavior1", writer)) + { + } + } + } + + class Stage1 : StageConnector + { + public Stage1(string instance, TextWriter writer) + { + this.writer = writer; + this.instance = instance; + } + + public override Task Invoke(IIncomingPhysicalMessageContext context, Func stage) + { + context.PrintInstanceWithRunSpecificIfPossible(instance, writer); + + var logicalMessageContext = new TestableIncomingLogicalMessageContext(); + logicalMessageContext.Extensions.Merge(context.Extensions); + + return stage(logicalMessageContext); + } + + string instance; + TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("Stage1", typeof(Stage1), "Stage1", b => new Stage1("stage1", writer)) + { + } + } + } + + class Behavior2 : IBehavior + { + public Behavior2(string instance, TextWriter writer) + { + this.instance = instance; + this.writer = writer; + } + + public Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + writer.WriteLine(instance); + return next(context); + } + + readonly string instance; + readonly TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("Behavior2", typeof(Behavior2), "Behavior2", b => new Behavior2("behavior2", writer)) + { + } + } + } + + class Stage2 : StageConnector + { + public Stage2(string instance, TextWriter writer) + { + this.writer = writer; + this.instance = instance; + } + + public override Task Invoke(IIncomingLogicalMessageContext context, Func stage) + { + context.PrintInstanceWithRunSpecificIfPossible(instance, writer); + + var dispatchContext = new TestableDispatchContext(); + dispatchContext.Extensions.Merge(context.Extensions); + + return stage(dispatchContext); + } + + string instance; + TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("Stage2", typeof(Stage2), "Stage2", b => new Stage2("stage2", writer)) + { + } + } + } + + class Terminator : PipelineTerminator + { + public Terminator(string instance, TextWriter writer) + { + this.instance = instance; + this.writer = writer; + } + + protected override Task Terminate(IDispatchContext context) + { + context.PrintInstanceWithRunSpecificIfPossible(instance, writer); + return TaskEx.CompletedTask; + } + + readonly string instance; + + readonly TextWriter writer; + + public class Registration : RegisterStep + { + public Registration(TextWriter writer) : base("Terminator", typeof(Terminator), "Terminator", b => new Terminator("terminator", writer)) + { + } + } + } + + class FakePipelineCache : IPipelineCache + { + public IPipeline Pipeline() + where TContext : IBehaviorContext + + { + return (IPipeline)new FakeBatchPipeline(); + } + } + + class FakeBatchPipeline : IPipeline + { + public Task Invoke(IBatchDispatchContext context) + { + return TaskEx.CompletedTask; + } + } + } + + static class LambdaExpressionPrettyPrint + { + public static string PrettyPrint(this List expression) + { + expression.Reverse(); + var sb = new StringBuilder(); + for (var i = 0; i < expression.Count; i++) + { + sb.AppendLine($"{new string(' ', i * 4)}{expression[i].ToString().TrimStart()},"); + } + return sb.ToString(); + } + } + + static class ExtendableExtensions + { + public const string RunSpecificKey = "RunSpecific"; + + public static void PrintInstanceWithRunSpecificIfPossible(this IExtendable context, string instance, TextWriter writer) + { + int runSpecific; + if (context.Extensions.TryGet(RunSpecificKey, out runSpecific)) + { + writer.WriteLine($"{instance}: {runSpecific}"); + } + else + { + writer.WriteLine(instance); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Pipeline/RegisterStepTests.cs b/src/NServiceBus.Core.Tests/Pipeline/RegisterStepTests.cs new file mode 100644 index 00000000000..b3af3ac6ad7 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Pipeline/RegisterStepTests.cs @@ -0,0 +1,126 @@ +namespace NServiceBus.Core.Tests.Pipeline +{ + using System; + using System.Threading.Tasks; + using Features; + using ObjectBuilder; + using NServiceBus.Pipeline; + using NUnit.Framework; + + public class RegisterStepTests + { + [Test] + public void Replace_WhenStepIdsDoNotMatch_ShouldThrowInvalidOperationException() + { + var registerStep = RegisterStep.Create("stepId 1", typeof(BehaviorA), "description"); + var replacement = new ReplaceStep("stepId 2", typeof(BehaviorB)); + + Assert.Throws(() => registerStep.Replace(replacement)); + } + + [Test] + public void Replace_ShouldReplaceBehaviorType() + { + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), "description"); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB)); + + registerStep.Replace(replacement); + + Assert.AreEqual(typeof(BehaviorB), registerStep.BehaviorType); + } + + [Test] + public void Replace_WhenReplacementContainsNoDescription_ShouldKeepOriginalDescription() + { + const string originalDescription = "description"; + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), originalDescription); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB)); + + registerStep.Replace(replacement); + + Assert.AreEqual(originalDescription, registerStep.Description); + } + + [Test] + public void Replace_WhenReplacementContainsEmptyDescription_ShouldKeepOriginalDescription() + { + const string originalDescription = "description"; + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), originalDescription); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB), " "); + + registerStep.Replace(replacement); + + Assert.AreEqual(originalDescription, registerStep.Description); + } + + [Test] + public void Replace_WhenReplacementContainsDescription_ShouldReplaceDescription() + { + const string replacementDescription = "new"; + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), "description"); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB), replacementDescription); + + registerStep.Replace(replacement); + + Assert.AreEqual(replacementDescription, registerStep.Description); + } + + [Test] + public void Replace_WhenReplacementProvidesNoFactory_ShouldBuildReplacementFromBuilder() + { + var originalBehaviorFactoryCalled = false; + Func originalBehaviorFactory = b => + { + originalBehaviorFactoryCalled = true; + return new BehaviorA(); + }; + + var builder = new FakeBuilder(typeof(BehaviorB)); + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), "description", originalBehaviorFactory); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB)); + + registerStep.Replace(replacement); + var behavior = registerStep.CreateBehavior(builder); + + Assert.IsFalse(originalBehaviorFactoryCalled); + Assert.IsInstanceOf(behavior); + } + + [Test] + public void Replace_WhenReplacementProvidedFactory_ShouldBuildReplacementFromFactory() + { + var replacementBehaviorFactoryCalled = false; + Func replacementBehaviorFactory = b => + { + replacementBehaviorFactoryCalled = true; + return new BehaviorB(); + }; + + var builder = new FakeBuilder(typeof(BehaviorB)); + var registerStep = RegisterStep.Create("pipelineStep", typeof(BehaviorA), "description", b => { throw new Exception(); }); + var replacement = new ReplaceStep("pipelineStep", typeof(BehaviorB), factoryMethod: replacementBehaviorFactory); + + registerStep.Replace(replacement); + var behavior = registerStep.CreateBehavior(builder); + + Assert.IsTrue(replacementBehaviorFactoryCalled); + Assert.IsInstanceOf(behavior); + } + + class BehaviorA : IBehavior + { + public Task Invoke(IRoutingContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + + class BehaviorB : IBehavior + { + public Task Invoke(IRoutingContext context, Func next) + { + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Recoverability/DefaultRecoverabilityPolicyTests.cs b/src/NServiceBus.Core.Tests/Recoverability/DefaultRecoverabilityPolicyTests.cs new file mode 100644 index 00000000000..ffc0dc7c9ab --- /dev/null +++ b/src/NServiceBus.Core.Tests/Recoverability/DefaultRecoverabilityPolicyTests.cs @@ -0,0 +1,168 @@ +namespace NServiceBus.Core.Tests.Recoverability +{ + using System; + using System.Collections.Generic; + using NUnit.Framework; + using Transport; + + [TestFixture] + public class DefaultRecoverabilityPolicyTests + { + [Test] + public void When_failure_is_caused_by_deserialization_exception_should_move_to_error() + { + var policy = CreatePolicy(maxImmediateRetries: 3, maxDelayedRetries: 3); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 1, exception: new MessageDeserializationException("failed")); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "Should move deserialization exception directly to error."); + } + + [Test] + public void When_max_immediate_retries_have_not_been_reached_should_return_immediate_retry() + { + var policy = CreatePolicy(maxImmediateRetries: 3); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 2); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "Should have one immediate retry left. It is second delivery attempt and we configured immediate reties to 2."); + } + + [Test] + public void When_max_immediate_retries_exceeded_should_return_delayed_retry() + { + var policy = CreatePolicy(2); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 3); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When max number of immediate retries exceeded should return DelayedRetry."); + } + + [Test] + public void When_max_immediate_retries_exceeded_but_delayed_retry_disabled_return_move_to_error() + { + var policy = CreatePolicy(maxImmediateRetries: 1, maxDelayedRetries: 0); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 3); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When max number of immediate retries exceeded and delayed retry disabled should return MoveToErrors."); + } + + [Test] + public void When_immediate_retries_turned_off_and_delayed_retry_policy_returns_delay_should_return_delayed_retry() + { + var deliveryDelay = TimeSpan.FromSeconds(10); + var policy = CreatePolicy(maxImmediateRetries: 0, delayedRetryDelay: deliveryDelay); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 1); + + var recoverabilityAction = policy(errorContext); + var delayedRetryAction = recoverabilityAction as DelayedRetry; + + Assert.IsInstanceOf(recoverabilityAction, "When immediate retries turned off and delayed retries left, recoverability policy should return DelayedRetry"); + Assert.AreEqual(deliveryDelay, delayedRetryAction.Delay); + } + + [Test] + public void When_immediate_retries_turned_off_and_delayed_retries_turned_off_should_return_move_to_errors() + { + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 0); + var errorContext = CreateErrorContext(); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When Immediate Retries turned off and Delayed Retry turned off should return MoveToErrors"); + } + + [Test] + public void When_immediate_retries_turned_off_and_delayed_retry_policy_returns_no_delay_should_return_move_to_errors() + { + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 0, delayedRetryDelay: TimeSpan.Zero); + var errorContext = CreateErrorContext(); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When Immediate Retries turned off and Delayed Retries policy returns no delay should return MoveToErrors"); + } + + [Test] + public void When_immediate_retries_turned_off_and_delayed_retry_not_available_should_return_move_to_errors() + { + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 0); + var errorContext = CreateErrorContext(); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When immediate retries turned off and delayed retries disabled should return MoveToErrors"); + } + + [Test] + public void When_delayed_retry_counter_header_exists_recoverability_policy_should_use_it() + { + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 1, delayedRetryDelay: TimeSpan.Zero); + var errorContext = CreateErrorContext(retryNumber: 1); + + var recoverabilityAction = policy(errorContext); + + Assert.IsInstanceOf(recoverabilityAction, "When Delayed Retries cunter in headers reaches max delayed retries, policy should return MoveToErrors"); + } + + [Test] + public void ShouldRetryTheSpecifiedTimesWithIncreasedDelay() + { + var baseDelay = TimeSpan.FromSeconds(10); + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 2, delayedRetryDelay: baseDelay); + + var errorContext = CreateErrorContext(retryNumber: 0); + var result1 = (DelayedRetry) policy(errorContext); + + errorContext = CreateErrorContext(retryNumber: 1); + var result2 = (DelayedRetry) policy(errorContext); + + errorContext = CreateErrorContext(retryNumber: 2); + var result3 = policy(errorContext); + + Assert.AreEqual(baseDelay, result1.Delay); + Assert.AreEqual(TimeSpan.FromSeconds(20), result2.Delay); + Assert.IsInstanceOf(result3); + } + + [Test] + public void ShouldCapTheRetryMaxTimeTo24Hours() + { + var now = DateTime.UtcNow; + var baseDelay = TimeSpan.FromSeconds(10); + + var policy = CreatePolicy(maxImmediateRetries: 0, maxDelayedRetries: 2, delayedRetryDelay: baseDelay); + + var moreThanADayAgo = now.AddHours(-24).AddTicks(-1); + var headers = new Dictionary + { + {Headers.DelayedRetriesTimestamp, DateTimeExtensions.ToWireFormattedString(moreThanADayAgo)} + }; + + var errorContext = CreateErrorContext(headers: headers); + + var result = policy(errorContext); + + Assert.IsInstanceOf(result); + } + + ErrorContext CreateErrorContext(int numberOfDeliveryAttempts = 1, int? retryNumber = null, Dictionary headers = null, Exception exception = null) + { + return new ErrorContext(exception ?? new Exception(), retryNumber.HasValue ? new Dictionary + { + {Headers.DelayedRetries, retryNumber.ToString()} + } : headers ?? new Dictionary(), "message-id", new byte[0], new TransportTransaction(), numberOfDeliveryAttempts); + } + + static Func CreatePolicy(int maxImmediateRetries = 2, int maxDelayedRetries = 2, TimeSpan? delayedRetryDelay = null) + { + var config = new RecoverabilityConfig(new ImmediateConfig(maxImmediateRetries), new DelayedConfig(maxDelayedRetries, delayedRetryDelay.GetValueOrDefault(TimeSpan.FromSeconds(2))), new FailedConfig("errorQueue")); + return context => DefaultRecoverabilityPolicy.Invoke(config, context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Recoverability/DelayedRetryExecutorTests.cs b/src/NServiceBus.Core.Tests/Recoverability/DelayedRetryExecutorTests.cs new file mode 100644 index 00000000000..591ff35ce02 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Recoverability/DelayedRetryExecutorTests.cs @@ -0,0 +1,145 @@ +namespace NServiceBus.Core.Tests.Recoverability +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DelayedDelivery; + using Extensibility; + using NUnit.Framework; + using Transport; + + [TestFixture] + public class DelayedRetryExecutorTests + { + [SetUp] + public void Setup() + { + dispatcher = new FakeDispatcher(); + } + + [Test] + public async Task Should_float_transport_transaction_to_dispatcher() + { + var transportTransaction = new TransportTransaction(); + var delayedRetryExecutor = CreateExecutor(nativeDeferralsOn: true); + var incomingMessage = CreateMessage(); + + await delayedRetryExecutor.Retry(incomingMessage, TimeSpan.Zero, transportTransaction); + + Assert.AreEqual(dispatcher.Transaction, transportTransaction); + } + + [Test] + public async Task When_native_delayed_delivery_should_add_delivery_constraint() + { + var delayedRetryExecutor = CreateExecutor(nativeDeferralsOn: true); + var incomingMessage = CreateMessage(); + var delay = TimeSpan.FromSeconds(42); + + await delayedRetryExecutor.Retry(incomingMessage, delay, new TransportTransaction()); + + var transportOperation = dispatcher.UnicastTransportOperations.Single(); + var deliveryConstraint = transportOperation.DeliveryConstraints.OfType().SingleOrDefault(); + + Assert.AreEqual(transportOperation.Destination, EndpointInputQueue); + Assert.IsNotNull(deliveryConstraint); + Assert.AreEqual(delay, deliveryConstraint.Delay); + } + + [Test] + public async Task When_no_native_delayed_delivery_should_route_message_to_timeout_manager() + { + var delayedRetryExecutor = CreateExecutor(nativeDeferralsOn: false); + var incomingMessage = CreateMessage(); + var delay = TimeSpan.FromSeconds(42); + + await delayedRetryExecutor.Retry(incomingMessage, delay, new TransportTransaction()); + + var transportOperation = dispatcher.UnicastTransportOperations.Single(); + var deliveryConstraint = transportOperation.DeliveryConstraints.OfType().SingleOrDefault(); + + Assert.IsNull(deliveryConstraint); + Assert.AreEqual(EndpointInputQueue, transportOperation.Message.Headers[TimeoutManagerHeaders.RouteExpiredTimeoutTo]); + Assert.That(DateTimeExtensions.ToUtcDateTime(transportOperation.Message.Headers[TimeoutManagerHeaders.Expire]), Is.GreaterThan(DateTime.UtcNow).And.LessThanOrEqualTo(DateTime.UtcNow + delay)); + Assert.AreEqual(TimeoutManagerAddress, transportOperation.Destination); + } + + [Test] + public async Task Should_update_retry_headers_when_present() + { + var delayedRetryExecutor = CreateExecutor(nativeDeferralsOn: true); + var originalHeadersTimestamp = DateTimeExtensions.ToWireFormattedString(new DateTime(2012, 12, 12, 0, 0, 0, DateTimeKind.Utc)); + + var incomingMessage = CreateMessage(new Dictionary + { + {Headers.DelayedRetries, "2"}, + {Headers.DelayedRetriesTimestamp, originalHeadersTimestamp} + }); + + var now = DateTime.UtcNow; + await delayedRetryExecutor.Retry(incomingMessage, TimeSpan.Zero, new TransportTransaction()); + + var outgoingMessageHeaders = dispatcher.UnicastTransportOperations.Single().Message.Headers; + + Assert.AreEqual("3", outgoingMessageHeaders[Headers.DelayedRetries]); + Assert.AreEqual("2", incomingMessage.Headers[Headers.DelayedRetries]); + + var utcDateTime = DateTimeExtensions.ToUtcDateTime(outgoingMessageHeaders[Headers.DelayedRetriesTimestamp]); + // the serialization removes precision which may lead to now being greater than the deserialized header value + var adjustedNow = DateTimeExtensions.ToUtcDateTime(DateTimeExtensions.ToWireFormattedString(now)); + Assert.That(utcDateTime, Is.GreaterThanOrEqualTo(adjustedNow)); + Assert.AreEqual(originalHeadersTimestamp, incomingMessage.Headers[Headers.DelayedRetriesTimestamp]); + } + + [Test] + public async Task Should_add_retry_headers_when_not_present() + { + var delayedRetryExecutor = CreateExecutor(nativeDeferralsOn: false); + var incomingMessage = CreateMessage(); + + await delayedRetryExecutor.Retry(incomingMessage, TimeSpan.Zero, new TransportTransaction()); + + var outgoingMessageHeaders = dispatcher.TransportOperations.UnicastTransportOperations.Single().Message.Headers; + + Assert.AreEqual("1", outgoingMessageHeaders[Headers.DelayedRetries]); + Assert.IsFalse(incomingMessage.Headers.ContainsKey(Headers.DelayedRetries)); + Assert.IsTrue(outgoingMessageHeaders.ContainsKey(Headers.DelayedRetriesTimestamp)); + Assert.IsFalse(incomingMessage.Headers.ContainsKey(Headers.DelayedRetriesTimestamp)); + } + + IncomingMessage CreateMessage(Dictionary headers = null) + { + return new IncomingMessage("messageId", headers ?? new Dictionary(), new byte[0]); + } + + DelayedRetryExecutor CreateExecutor(bool nativeDeferralsOn = true) + { + return new DelayedRetryExecutor(EndpointInputQueue, dispatcher, nativeDeferralsOn ? null : TimeoutManagerAddress); + } + + FakeDispatcher dispatcher; + + const string TimeoutManagerAddress = "timeout handling endpoint"; + const string EndpointInputQueue = "endpoint input queue"; + + class FakeDispatcher : IDispatchMessages + { + public TransportOperations TransportOperations { get; private set; } + + public List UnicastTransportOperations => TransportOperations.UnicastTransportOperations; + + public ContextBag ContextBag { get; private set; } + + public TransportTransaction Transaction { get; private set; } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + TransportOperations = outgoingMessages; + ContextBag = context; + Transaction = transaction; + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Recoverability/MoveToErrorsExecutorTests.cs b/src/NServiceBus.Core.Tests/Recoverability/MoveToErrorsExecutorTests.cs new file mode 100644 index 00000000000..809589f5ee6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Recoverability/MoveToErrorsExecutorTests.cs @@ -0,0 +1,158 @@ +namespace NServiceBus.Core.Tests.Recoverability +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Extensibility; + using NUnit.Framework; + using Transport; + + [TestFixture] + public class MoveToErrorsExecutorTests + { + [SetUp] + public void Setup() + { + dispatcher = new FakeDispatcher(); + staticFaultMetadata = new Dictionary(); + moveToErrorsExecutor = new MoveToErrorsExecutor(dispatcher, staticFaultMetadata, headers => { }); + } + + [Test] + public async Task MoveToErrorQueue_should_dispatch_message_to_error_queue() + { + var customErrorQueue = "random_error_queue"; + var transportTransaction = new TransportTransaction(); + var incomingMessage = new IncomingMessage("messageId", new Dictionary(), new byte[0]); + + await moveToErrorsExecutor.MoveToErrorQueue(customErrorQueue, incomingMessage, new Exception(), transportTransaction); + + Assert.That(dispatcher.TransportOperations.MulticastTransportOperations.Count(), Is.EqualTo(0)); + Assert.That(dispatcher.TransportOperations.UnicastTransportOperations.Count(), Is.EqualTo(1)); + Assert.That(dispatcher.Transaction, Is.EqualTo(transportTransaction)); + + var outgoingMessage = dispatcher.TransportOperations.UnicastTransportOperations.Single(); + Assert.That(outgoingMessage.Destination, Is.EqualTo(customErrorQueue)); + Assert.That(outgoingMessage.Message.MessageId, Is.EqualTo(incomingMessage.MessageId)); + } + + [Test] + public async Task MoveToErrorQueue_should_preserve_incoming_message_headers() + { + var incomingMessageHeaders = new Dictionary + { + {"key1", "value1"}, + {"key2", "value2"} + }; + + var incomingMessage = new IncomingMessage("messageId", incomingMessageHeaders, new byte[0]); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, new Exception(), new TransportTransaction()); + + var outgoingMessage = dispatcher.TransportOperations.UnicastTransportOperations.Single(); + Assert.That(incomingMessage.Headers, Is.SubsetOf(outgoingMessage.Message.Headers)); + } + + [Test] + public async Task MoveToErrorQueue_should_dispatch_original_message_body() + { + var originalMessageBody = Encoding.UTF8.GetBytes("message body"); + var incomingMessage = new IncomingMessage("messageId", new Dictionary(), originalMessageBody); + incomingMessage.UpdateBody(Encoding.UTF8.GetBytes("new body")); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, new Exception(), new TransportTransaction()); + + var outgoingMessage = dispatcher.TransportOperations.UnicastTransportOperations.Single(); + Assert.That(outgoingMessage.Message.Body, Is.EqualTo(originalMessageBody)); + } + + [Test] + public async Task MoveToErrorQueue_should_remove_known_retry_headers() + { + var retryHeaders = new Dictionary + { + {Headers.ImmediateRetries, "42"}, + {Headers.DelayedRetries, "21"} + }; + var incomingMessage = new IncomingMessage("messageId", retryHeaders, new byte[0]); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, new Exception(), new TransportTransaction()); + + var outgoingMessage = dispatcher.TransportOperations.UnicastTransportOperations.Single(); + Assert.That(outgoingMessage.Message.Headers.Keys, Does.Not.Contain(Headers.ImmediateRetries)); + Assert.That(outgoingMessage.Message.Headers.Keys, Does.Not.Contain(Headers.DelayedRetries)); + } + + [Test] + public async Task MoveToErrorQueue_should_add_exception_headers() + { + var incomingMessage = new IncomingMessage("messageId", new Dictionary(), new byte[0]); + var exception = new InvalidOperationException("test exception"); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, exception, new TransportTransaction()); + + var outgoingMessageHeaders = dispatcher.TransportOperations.UnicastTransportOperations.Single().Message.Headers; + // we only test presence of some exception headers set by ExceptionHeaderHelper + Assert.That(outgoingMessageHeaders, Contains.Key("NServiceBus.ExceptionInfo.ExceptionType")); + Assert.That(outgoingMessageHeaders, Contains.Key("NServiceBus.ExceptionInfo.Message")); + Assert.That(outgoingMessageHeaders, Contains.Key("NServiceBus.ExceptionInfo.StackTrace")); + // check for leaking headers + Assert.That(incomingMessage.Headers.ContainsKey("NServiceBus.ExceptionInfo.ExceptionType"), Is.False); + } + + [Test] + public async Task MoveToErrorQueue_should_add_static_fault_info_to_headers() + { + staticFaultMetadata.Add("staticFaultMetadataKey", "staticFaultMetadataValue"); + var incomingMessage = new IncomingMessage("messageId", new Dictionary(), new byte[0]); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, new Exception(), new TransportTransaction()); + + var outgoingMessageHeaders = dispatcher.TransportOperations.UnicastTransportOperations.Single().Message.Headers; + Assert.That(outgoingMessageHeaders, Contains.Item(new KeyValuePair("staticFaultMetadataKey", "staticFaultMetadataValue"))); + // check for leaking headers + Assert.That(incomingMessage.Headers.ContainsKey("staticFaultMetadataKey"), Is.False); + } + + [Test] + public async Task MoveToErrorQueue_should_apply_header_customizations_before_dispatch() + { + staticFaultMetadata.Add("staticFaultMetadataKey", "staticFaultMetadataValue"); + var incomingMessage = new IncomingMessage("messageId", new Dictionary(), new byte[0]); + var exception = new InvalidOperationException("test exception"); + + Dictionary passedInHeaders = null; + moveToErrorsExecutor = new MoveToErrorsExecutor(dispatcher, staticFaultMetadata, headers => { passedInHeaders = headers; }); + + await moveToErrorsExecutor.MoveToErrorQueue(ErrorQueueAddress, incomingMessage, exception, new TransportTransaction()); + + Assert.NotNull(passedInHeaders); + Assert.That(passedInHeaders, Contains.Key("staticFaultMetadataKey")); + Assert.That(passedInHeaders, Contains.Key("NServiceBus.ExceptionInfo.Message")); + } + + MoveToErrorsExecutor moveToErrorsExecutor; + FakeDispatcher dispatcher; + Dictionary staticFaultMetadata; + const string ErrorQueueAddress = "errorQ"; + + class FakeDispatcher : IDispatchMessages + { + public TransportOperations TransportOperations { get; private set; } + + public ContextBag ContextBag { get; private set; } + + public TransportTransaction Transaction { get; private set; } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + TransportOperations = outgoingMessages; + ContextBag = context; + Transaction = transaction; + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Recoverability/RecoverabilityExecutorTests.cs b/src/NServiceBus.Core.Tests/Recoverability/RecoverabilityExecutorTests.cs new file mode 100644 index 00000000000..960ec73fca3 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Recoverability/RecoverabilityExecutorTests.cs @@ -0,0 +1,239 @@ +namespace NServiceBus.Core.Tests.Recoverability +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NUnit.Framework; + using Transport; + + [TestFixture] + public class RecoverabilityExecutorTests + { + [SetUp] + public void SetUp() + { + dispatcher = new FakeDispatcher(); + eventAggregator = new FakeEventAggregator(); + } + + [Test] + public async Task When_notification_turned_off_no_notification_should_be_raised() + { + var policy = RetryPolicy.Return( + actions: new RecoverabilityAction[] + { + RecoverabilityAction.ImmediateRetry(), + RecoverabilityAction.DelayedRetry(TimeSpan.FromSeconds(10)), + RecoverabilityAction.MoveToError("errorQueue") + }); + var executor = CreateExecutor(policy, raiseNotifications: false); + var errorContext = CreateErrorContext(); + + await executor.Invoke(errorContext); //force retry + await executor.Invoke(errorContext); //force delayed retry + await executor.Invoke(errorContext); //force move to errors + + Assert.IsNull(eventAggregator.GetNotification()); + Assert.IsNull(eventAggregator.GetNotification()); + } + + [Test] + public async Task When_failure_is_handled_with_immediate_retries_notification_should_be_raised() + { + var recoverabilityExecutor = CreateExecutor(RetryPolicy.AlwaysRetry()); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 1, exceptionMessage: "test", messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(0, failure.Attempt); + Assert.IsTrue(failure.IsImmediateRetry); + Assert.AreEqual("test", failure.Exception.Message); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + [Test] + public async Task When_failure_is_handled_with_delayed_retries_notification_should_be_raised() + { + var recoverabilityExecutor = CreateExecutor(RetryPolicy.AlwaysDelay(TimeSpan.FromSeconds(10))); + var errorContext = CreateErrorContext(numberOfDeliveryAttempts: 1, exceptionMessage: "test", messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(1, failure.Attempt); + Assert.IsFalse(failure.IsImmediateRetry); + Assert.AreEqual("test", failure.Exception.Message); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + [Test] + public async Task When_failure_is_handled_by_moving_to_errors_notification_should_be_raised() + { + var recoverabilityExecutor = CreateExecutor(RetryPolicy.AlwaysMoveToErrors()); + var errorContext = CreateErrorContext(exceptionMessage: "test", messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual("test", failure.Exception.Message); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + [Test] + public async Task When_delayed_retries_not_supported_but_policy_demands_it_should_move_to_errors() + { + var recoverabilityExecutor = CreateExecutor( + RetryPolicy.AlwaysDelay(TimeSpan.FromDays(1)), + delayedRetriesSupported: false); + var errorContext = CreateErrorContext(messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(1, eventAggregator.NotificationsRaised.Count); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + [Test] + public async Task When_immediate_retries_not_supported_but_policy_demands_it_should_move_to_errors() + { + var recoverabilityExecutor = CreateExecutor( + RetryPolicy.AlwaysRetry(), + immediateRetriesSupported: false); + var errorContext = CreateErrorContext(messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(1, eventAggregator.NotificationsRaised.Count); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + [Test] + public async Task When_unsupported_action_returned_should_move_to_errors() + { + var recoverabilityExecutor = CreateExecutor( + RetryPolicy.Unsupported()); + var errorContext = CreateErrorContext(messageId: "message-id"); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(1, eventAggregator.NotificationsRaised.Count); + Assert.AreEqual("message-id", failure.Message.MessageId); + } + + public async Task When_moving_to_custom_error_queue_custom_error_queue_address_should_be_set_on_notification() + { + var customErrorQueueAddress = "custom-error-queue"; + var recoverabilityExecutor = CreateExecutor(RetryPolicy.AlwaysMoveToErrors(customErrorQueueAddress)); + var errorContext = CreateErrorContext(); + + await recoverabilityExecutor.Invoke(errorContext); + + var failure = eventAggregator.GetNotification(); + + Assert.AreEqual(1, eventAggregator.NotificationsRaised.Count); + Assert.AreEqual(customErrorQueueAddress, failure.ErrorQueue); + } + + static ErrorContext CreateErrorContext(Exception raisedException = null, string exceptionMessage = "default-message", string messageId = "default-id", int numberOfDeliveryAttempts = 1) + { + return new ErrorContext(raisedException ?? new Exception(exceptionMessage), new Dictionary(), messageId, new byte[0], new TransportTransaction(), numberOfDeliveryAttempts); + } + + RecoverabilityExecutor CreateExecutor(Func policy, bool delayedRetriesSupported = true, bool immediateRetriesSupported = true, bool raiseNotifications = true) + { + return new RecoverabilityExecutor( + raiseNotifications, + immediateRetriesSupported, + delayedRetriesSupported, + policy, + new RecoverabilityConfig(new ImmediateConfig(0), new DelayedConfig(0, TimeSpan.Zero), new FailedConfig(ErrorQueueAddress)), + eventAggregator, + delayedRetriesSupported ? new DelayedRetryExecutor(InputQueueAddress, dispatcher) : null, + new MoveToErrorsExecutor(dispatcher, new Dictionary(), headers => { })); + } + + FakeDispatcher dispatcher; + FakeEventAggregator eventAggregator; + + static string ErrorQueueAddress = "error-queue"; + static string InputQueueAddress = "input-queue"; + + class RetryPolicy + { + RetryPolicy(RecoverabilityAction[] actions) + { + this.actions = new Queue(actions); + } + + public RecoverabilityAction Invoke(RecoverabilityConfig config, ErrorContext errorContext) + { + return actions.Dequeue(); + } + + public static Func AlwaysDelay(TimeSpan delay) + { + return new RetryPolicy(new[] + { + RecoverabilityAction.DelayedRetry(delay) + }).Invoke; + } + + public static Func AlwaysMoveToErrors(string errorQueueAddress = "errorQueue") + { + return new RetryPolicy(new[] + { + RecoverabilityAction.MoveToError(errorQueueAddress) + }).Invoke; + } + + public static Func AlwaysRetry() + { + return new RetryPolicy(new[] + { + RecoverabilityAction.ImmediateRetry() + }).Invoke; + } + + public static Func Return(RecoverabilityAction[] actions) + { + return new RetryPolicy(actions).Invoke; + } + + public static Func Unsupported() + { + return new RetryPolicy(new[] + { + new UnsupportedAction() + }).Invoke; + } + + Queue actions; + } + + class UnsupportedAction : RecoverabilityAction + { + } + + class FakeDispatcher : IDispatchMessages + { + public TransportOperations TransportOperations { get; private set; } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + TransportOperations = outgoingMessages; + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/RedirectHelper.cs b/src/NServiceBus.Core.Tests/RedirectHelper.cs index 0660b50c651..fd726c0dc67 100644 --- a/src/NServiceBus.Core.Tests/RedirectHelper.cs +++ b/src/NServiceBus.Core.Tests/RedirectHelper.cs @@ -7,8 +7,7 @@ [SetUpFixture] public class RedirectHelper { - - [SetUp] + [OneTimeSetUp] public void Initialize() { AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/FakeOutboxStorage.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/FakeOutboxStorage.cs new file mode 100644 index 00000000000..43783e37dcc --- /dev/null +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/FakeOutboxStorage.cs @@ -0,0 +1,41 @@ +namespace NServiceBus.Core.Tests.Reliability.Outbox +{ + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Outbox; + + class FakeOutboxStorage : IOutboxStorage + { + public OutboxMessage ExistingMessage { get; set; } + public OutboxMessage StoredMessage { get; set; } + + public bool WasDispatched { get; set; } + + public Task Get(string messageId, ContextBag options) + { + if (ExistingMessage != null && ExistingMessage.MessageId == messageId) + { + return Task.FromResult(ExistingMessage); + } + + return Task.FromResult(default(OutboxMessage)); + } + + public Task Store(OutboxMessage message, OutboxTransaction transaction, ContextBag options) + { + StoredMessage = message; + return TaskEx.CompletedTask; + } + + public Task SetAsDispatched(string messageId, ContextBag options) + { + WasDispatched = true; + return TaskEx.CompletedTask; + } + + public Task BeginTransaction(ContextBag context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageProcessingConnectorTests.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageProcessingConnectorTests.cs new file mode 100644 index 00000000000..8d46f63780b --- /dev/null +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageProcessingConnectorTests.cs @@ -0,0 +1,176 @@ +namespace NServiceBus.Core.Tests.Reliability.Outbox +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using NServiceBus.Outbox; + using NServiceBus.Performance.TimeToBeReceived; + using NServiceBus.Pipeline; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + using Transport; + using TransportOperation = Transport.TransportOperation; + + [TestFixture] + public class TransportReceiveToPhysicalMessageProcessingConnectorTests + { + [Test] + public async Task Should_honor_stored_delivery_constraints() + { + var messageId = "id"; + var options = new Dictionary(); + var deliverTime = DateTime.UtcNow.AddDays(1); + var maxTime = TimeSpan.FromDays(1); + + options["Destination"] = "test"; + + options["NonDurable"] = true.ToString(); + options["DeliverAt"] = DateTimeExtensions.ToWireFormattedString(deliverTime); + options["DelayDeliveryFor"] = TimeSpan.FromSeconds(10).ToString(); + options["TimeToBeReceived"] = maxTime.ToString(); + + fakeOutbox.ExistingMessage = new OutboxMessage(messageId, new[] + { + new NServiceBus.Outbox.TransportOperation("x", options, new byte[0], new Dictionary()) + }); + + var context = CreateContext(fakeBatchPipeline, messageId); + + await Invoke(context); + + Assert.True(fakeBatchPipeline.TransportOperations.First().DeliveryConstraints.Any(c => c is NonDurableDelivery)); + + DelayDeliveryWith delayDeliveryWith; + + Assert.True(fakeBatchPipeline.TransportOperations.First().DeliveryConstraints.TryGet(out delayDeliveryWith)); + Assert.AreEqual(TimeSpan.FromSeconds(10), delayDeliveryWith.Delay); + + DoNotDeliverBefore doNotDeliverBefore; + + Assert.True(fakeBatchPipeline.TransportOperations.First().DeliveryConstraints.TryGet(out doNotDeliverBefore)); + Assert.AreEqual(deliverTime.ToString(), doNotDeliverBefore.At.ToString()); + + DiscardIfNotReceivedBefore discard; + + Assert.True(fakeBatchPipeline.TransportOperations.First().DeliveryConstraints.TryGet(out discard)); + Assert.AreEqual(maxTime, discard.MaxTime); + + Assert.Null(fakeOutbox.StoredMessage); + } + + [Test] + public async Task Should_honor_stored_direct_routing() + { + var messageId = "id"; + var options = new Dictionary(); + + options["Destination"] = "myEndpoint"; + + fakeOutbox.ExistingMessage = new OutboxMessage(messageId, new[] + { + new NServiceBus.Outbox.TransportOperation("x", options, new byte[0], new Dictionary()) + }); + + var context = CreateContext(fakeBatchPipeline, messageId); + + await Invoke(context); + + var routing = fakeBatchPipeline.TransportOperations.First().AddressTag as UnicastAddressTag; + Assert.NotNull(routing); + Assert.AreEqual("myEndpoint", routing.Destination); + Assert.Null(fakeOutbox.StoredMessage); + } + + + [Test] + public async Task Should_honor_stored_pubsub_routing() + { + var messageId = "id"; + var options = new Dictionary(); + + options["EventType"] = typeof(MyEvent).AssemblyQualifiedName; + + fakeOutbox.ExistingMessage = new OutboxMessage(messageId, new[] + { + new NServiceBus.Outbox.TransportOperation("x", options, new byte[0], new Dictionary()) + }); + + var context = CreateContext(fakeBatchPipeline, messageId); + + await Invoke(context); + + var routing = fakeBatchPipeline.TransportOperations.First().AddressTag as MulticastAddressTag; + Assert.NotNull(routing); + Assert.AreEqual(typeof(MyEvent), routing.MessageType); + Assert.Null(fakeOutbox.StoredMessage); + } + + static ITransportReceiveContext CreateContext(FakeBatchPipeline pipeline, string messageId) + { + var context = new TestableTransportReceiveContext + { + Message = new IncomingMessage(messageId, new Dictionary(), new byte[0]) + }; + + context.Extensions.Set(new FakePipelineCache(pipeline)); + + return context; + } + + [SetUp] + public void SetUp() + { + fakeOutbox = new FakeOutboxStorage(); + fakeBatchPipeline = new FakeBatchPipeline(); + + behavior = new TransportReceiveToPhysicalMessageProcessingConnector(fakeOutbox); + } + + Task Invoke(ITransportReceiveContext context) + { + return behavior.Invoke(context, c => TaskEx.CompletedTask); + } + + TransportReceiveToPhysicalMessageProcessingConnector behavior; + + FakeBatchPipeline fakeBatchPipeline; + FakeOutboxStorage fakeOutbox; + + class MyEvent + { + } + + class FakePipelineCache : IPipelineCache + { + public FakePipelineCache(IPipeline pipeline) + { + this.pipeline = pipeline; + } + + public IPipeline Pipeline() + where TContext : IBehaviorContext + + { + return (IPipeline) pipeline; + } + + IPipeline pipeline; + } + + class FakeBatchPipeline : IPipeline + { + public IEnumerable TransportOperations { get; set; } + + public Task Invoke(IBatchDispatchContext context) + { + TransportOperations = context.Operations; + + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/ApplyReplyToAddressBehaviorTests.cs b/src/NServiceBus.Core.Tests/Routing/ApplyReplyToAddressBehaviorTests.cs new file mode 100644 index 00000000000..9202ef8093a --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/ApplyReplyToAddressBehaviorTests.cs @@ -0,0 +1,154 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Pipeline; + using NUnit.Framework; + using Testing; + using Transport; + + [TestFixture] + public class ApplyReplyToAddressBehaviorTests + { + [Test] + public async Task Should_use_public_return_address_if_specified() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", "PublicAddress", null); + var options = new SendOptions(); + var context = CreateContext(options); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("PublicAddress", context.Headers[Headers.ReplyToAddress]); + } + + static IOutgoingLogicalMessageContext CreateContext(ExtendableOptions options) + { + var context = new TestableOutgoingLogicalMessageContext + { + Extensions = options.Context + }; + + return context; + } + + [Test] + public async Task Should_default_to_setting_the_reply_to_header_to_this_endpoint() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", null, null); + var options = new SendOptions(); + var context = CreateContext(options); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("MyEndpoint", context.Headers[Headers.ReplyToAddress]); + } + + [Test] + public async Task Should_set_the_reply_to_header_to_this_endpoint_when_requested() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", null, null); + var options = new SendOptions(); + + options.RouteReplyToAnyInstance(); + + var context = CreateContext(options); + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("MyEndpoint", context.Headers[Headers.ReplyToAddress]); + } + + [Test] + public async Task Should_set_the_reply_to_header_to_this_instance_when_requested() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", null, null); + var options = new SendOptions(); + + options.RouteReplyToThisInstance(); + + var context = CreateContext(options); + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("MyInstance", context.Headers[Headers.ReplyToAddress]); + } + + [Test] + public async Task Should_set_the_reply_to_header_a_specified_address_when_requested() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", null, null); + var options = new SendOptions(); + + options.RouteReplyTo("Destination"); + + var context = CreateContext(options); + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("Destination", context.Headers[Headers.ReplyToAddress]); + } + + [Test] + public async Task Should_set_the_reply_to_distributor_address_when_message_comes_from_a_distributor() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", "MyPublicAddress", "MyDistributor"); + var options = new SendOptions(); + var context = CreateContext(options); + + context.Extensions.Set(new IncomingMessage("ID", new Dictionary + { + {LegacyDistributorHeaders.WorkerSessionId, "SessionID"} + }, new byte[0])); + + var state = context.Extensions.GetOrCreate(); + state.Option = ApplyReplyToAddressBehavior.RouteOption.RouteReplyToThisInstance; + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual("MyDistributor", context.Headers[Headers.ReplyToAddress]); + } + + [Test] + public async Task Should_throw_when_trying_to_route_replies_to_this_instance_when_no_instance_id_is_used() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", null, null, null); + var options = new SendOptions(); + + options.RouteReplyToThisInstance(); + + var context = CreateContext(options); + + try + { + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + Assert.Fail("Expected exception"); + } + catch (Exception) + { + Assert.Pass(); + } + } + + [Test] + public async Task Should_throw_when_conflicting_settings_are_specified() + { + var behavior = new ApplyReplyToAddressBehavior("MyEndpoint", "MyInstance", null, null); + + try + { + var options = new SendOptions(); + var context = CreateContext(options); + + options.RouteReplyToAnyInstance(); + options.RouteReplyToThisInstance(); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + Assert.Fail("Expected exception"); + } + catch (Exception) + { + Assert.Pass(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/AssemblyRouteSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/AssemblyRouteSourceTests.cs new file mode 100644 index 00000000000..879a2baa787 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/AssemblyRouteSourceTests.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using System.Reflection; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class AssemblyRouteSourceTests + { + [Test] + public void It_returns_only_message_types() + { + var source = new AssemblyRouteSource(Assembly.GetExecutingAssembly(), UnicastRoute.CreateFromEndpointName("Destination")); + var routes = source.GenerateRoutes(new Conventions()).ToArray(); + var routeTypes = routes.Select(r => r.MessageType); + + CollectionAssert.DoesNotContain(routeTypes, typeof(NonMessage)); + } + + + [Test] + public void It_throws_if_specified_assembly_contains_no_message_types() + { + var source = new AssemblyRouteSource(typeof(string).Assembly, UnicastRoute.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateRoutes(new Conventions()).ToArray(), Throws.Exception.Message.Contains("Cannot configure routing for assembly")); + } + + class NonMessage + { + } + } +} diff --git a/src/NServiceBus.Core.Tests/Routing/BestPracticesOptionExtensionsTests.cs b/src/NServiceBus.Core.Tests/Routing/BestPracticesOptionExtensionsTests.cs new file mode 100644 index 00000000000..626d56d64ab --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/BestPracticesOptionExtensionsTests.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using NUnit.Framework; + + [TestFixture] + public class BestPracticesOptionExtensionsTests + { + [Test] + public void IgnoredBestPractices_Should_Return_False_When_Not_Disabled_Best_Practice_Enforcement() + { + var options = new SendOptions(); + + Assert.IsFalse(options.IgnoredBestPractices()); + } + + [Test] + public void IgnoredBestPractices_Should_Return_True_When_Disabled_Best_Practice_Enforcement() + { + var options = new PublishOptions(); + + options.DoNotEnforceBestPractices(); + + Assert.IsTrue(options.IgnoredBestPractices()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/DetermineRouteForPublishBehaviorTests.cs b/src/NServiceBus.Core.Tests/Routing/DetermineRouteForPublishBehaviorTests.cs new file mode 100644 index 00000000000..1115b31beba --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/DetermineRouteForPublishBehaviorTests.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + + public class DetermineRouteForPublishBehaviorTests + { + [Test] + public async Task Should_use_to_all_subscribers_strategy() + { + var behavior = new MulticastPublishRouterBehavior(); + + var context = new TestableOutgoingPublishContext + { + Message = new OutgoingLogicalMessage(typeof(MyEvent), new MyEvent()) + }; + + MulticastAddressTag addressTag = null; + await behavior.Invoke(context, _ => + { + addressTag = (MulticastAddressTag)_.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual(typeof(MyEvent), addressTag.MessageType); + } + + class MyEvent + { } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/DetermineRouteForReplyBehaviorTests.cs b/src/NServiceBus.Core.Tests/Routing/DetermineRouteForReplyBehaviorTests.cs new file mode 100644 index 00000000000..939bda2f6e6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/DetermineRouteForReplyBehaviorTests.cs @@ -0,0 +1,89 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + using Transport; + + [TestFixture] + public class DetermineRouteForReplyBehaviorTests + { + [Test] + public async Task Should_default_to_reply_address_of_incoming_message_for_replies() + { + var behavior = new UnicastReplyRouterConnector(); + + var context = CreateContext(new OutgoingLogicalMessage(typeof(MyReply), new MyReply())); + + context.Extensions.Set(new IncomingMessage( + "id", + new Dictionary + { + {Headers.ReplyToAddress, "ReplyAddressOfIncomingMessage"} + }, + new byte[0])); + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag) c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("ReplyAddressOfIncomingMessage", addressTag.Destination); + } + + [Test] + public void Should_throw_if_incoming_message_has_no_reply_to_address() + { + var behavior = new UnicastReplyRouterConnector(); + + var context = CreateContext(new OutgoingLogicalMessage(typeof(MyReply), new MyReply())); + + context.Extensions.Set(new IncomingMessage( + "id", + new Dictionary(), + new byte[0])); + + Assert.That(async () => await behavior.Invoke(context, _ => TaskEx.CompletedTask), Throws.InstanceOf().And.Message.Contains(typeof(MyReply).FullName)); + } + + [Test] + public async Task Should_use_explicit_route_for_replies_if_present() + { + var behavior = new UnicastReplyRouterConnector(); + var options = new ReplyOptions(); + + options.SetDestination("CustomReplyToAddress"); + + var context = CreateContext(new OutgoingLogicalMessage(typeof(MyReply), new MyReply())); + context.Extensions = options.Context; + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag) c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("CustomReplyToAddress", addressTag.Destination); + } + + TestableOutgoingReplyContext CreateContext(OutgoingLogicalMessage message) + { + return new TestableOutgoingReplyContext + { + Message = message + }; + } + + class MyReply + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/DistributionPolicyTests.cs b/src/NServiceBus.Core.Tests/Routing/DistributionPolicyTests.cs new file mode 100644 index 00000000000..1e6ef27bab6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/DistributionPolicyTests.cs @@ -0,0 +1,59 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class DistributionPolicyTests + { + [Test] + public void When_no_strategy_configured_for_endpoint_should_use_round_robbin_strategy() + { + IDistributionPolicy policy = new DistributionPolicy(); + + var result = policy.GetDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void When_strategy_configured_for_endpoint_should_use_configured_strategy() + { + var p = new DistributionPolicy(); + var configuredStrategy = new FakeDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + p.SetDistributionStrategy(configuredStrategy); + + IDistributionPolicy policy = p; + var result = policy.GetDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + + Assert.That(result, Is.EqualTo(configuredStrategy)); + } + + [Test] + public void When_multiple_strategies_configured_endpoint_should_use_last_configured_strategy() + { + var p = new DistributionPolicy(); + var strategy1 = new FakeDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + var strategy2 = new FakeDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + p.SetDistributionStrategy(strategy1); + p.SetDistributionStrategy(strategy2); + + IDistributionPolicy policy = p; + var result = policy.GetDistributionStrategy("SomeEndpoint", DistributionStrategyScope.Send); + + Assert.That(result, Is.EqualTo(strategy2)); + } + + class FakeDistributionStrategy : DistributionStrategy + { + public FakeDistributionStrategy(string endpoint, DistributionStrategyScope scope) : base(endpoint, scope) + { + } + + public override string SelectReceiver(string[] receiverAddresses) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/EndpointInstanceTests.cs b/src/NServiceBus.Core.Tests/Routing/EndpointInstanceTests.cs new file mode 100644 index 00000000000..438f6c97d2a --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/EndpointInstanceTests.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class EndpointInstanceTests + { + [Test] + public void Two_instances_with_same_properties_are_equal() + { + var i1 = new EndpointInstance("Endpoint").SetProperty("P1", "V1").SetProperty("P2", "V2"); + var i2 = new EndpointInstance("Endpoint").SetProperty("P2", "V2").SetProperty("P1", "V1"); + + Assert.IsTrue(i1.Equals(i2)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/EndpointInstancesTests.cs b/src/NServiceBus.Core.Tests/Routing/EndpointInstancesTests.cs new file mode 100644 index 00000000000..7d27df38618 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/EndpointInstancesTests.cs @@ -0,0 +1,71 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using System.Linq; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class EndpointInstancesTests + { + [Test] + public void Should_add_instances_grouped_by_endpoint_name() + { + var instances = new EndpointInstances(); + const string endpointName1 = "EndpointA"; + const string endpointName2 = "EndpointB"; + instances.AddOrReplaceInstances("A", new List + { + new EndpointInstance(endpointName1), + new EndpointInstance(endpointName2) + }); + + var salesInstances = instances.FindInstances(endpointName1); + Assert.AreEqual(1, salesInstances.Count()); + + var otherInstances = instances.FindInstances(endpointName2); + Assert.AreEqual(1, otherInstances.Count()); + } + + [Test] + public void Should_return_instances_configured_by_static_route() + { + var instances = new EndpointInstances(); + var sales = "Sales"; + instances.AddOrReplaceInstances("A", new List + { + new EndpointInstance(sales, "1"), + new EndpointInstance(sales, "2") + }); + + var salesInstances = instances.FindInstances(sales); + Assert.AreEqual(2, salesInstances.Count()); + } + + [Test] + public void Should_filter_out_duplicate_instances() + { + var instances = new EndpointInstances(); + var sales = "Sales"; + instances.AddOrReplaceInstances("A", new List + { + new EndpointInstance(sales, "dup"), + new EndpointInstance(sales, "dup") + }); + + var salesInstances = instances.FindInstances(sales); + Assert.AreEqual(1, salesInstances.Count()); + } + + [Test] + public void Should_default_to_single_instance_when_not_configured() + { + var instances = new EndpointInstances(); + var salesInstancess = instances.FindInstances("Sales"); + + var singleInstance = salesInstancess.Single(); + Assert.IsNull(singleInstance.Discriminator); + Assert.IsEmpty(singleInstance.Properties); + } + } +} diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscribeTerminatorTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscribeTerminatorTests.cs new file mode 100644 index 00000000000..b00e3875d2c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscribeTerminatorTests.cs @@ -0,0 +1,116 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using Transport; + using NUnit.Framework; + using Testing; + using Unicast.Queuing; + + [TestFixture] + public class MessageDrivenSubscribeTerminatorTests + { + [SetUp] + public void SetUp() + { + var publishers = new Publishers(); + publishers.AddOrReplacePublishers("A", new List {new PublisherTableEntry(typeof(object), PublisherAddress.CreateFromPhysicalAddresses("publisher1"))}); + router = new SubscriptionRouter(publishers, new EndpointInstances(), i => i.ToString()); + dispatcher = new FakeDispatcher(); + subscribeTerminator = new MessageDrivenSubscribeTerminator(router, "replyToAddress", "Endpoint", dispatcher); + } + + [Test] + public async Task Should_include_TimeSent_and_Version_headers() + { + var unsubscribeTerminator = new MessageDrivenUnsubscribeTerminator(router, "replyToAddress", "Endpoint", dispatcher); + + await subscribeTerminator.Invoke(new TestableSubscribeContext(), c => TaskEx.CompletedTask); + await unsubscribeTerminator.Invoke(new TestableUnsubscribeContext(), c => TaskEx.CompletedTask); + + foreach (var dispatchedTransportOperation in dispatcher.DispatchedTransportOperations) + { + var unicastTransportOperations = dispatchedTransportOperation.UnicastTransportOperations; + var operations = new List(unicastTransportOperations); + + Assert.IsTrue(operations[0].Message.Headers.ContainsKey(Headers.TimeSent)); + Assert.IsTrue(operations[0].Message.Headers.ContainsKey(Headers.NServiceBusVersion)); + } + } + + [Test] + public async Task Should_Dispatch_for_all_publishers() + { + await subscribeTerminator.Invoke(new TestableSubscribeContext(), c => TaskEx.CompletedTask); + + Assert.AreEqual(1, dispatcher.DispatchedTransportOperations.Count); + } + + [Test] + public async Task Should_Dispatch_according_to_max_retries_when_dispatch_fails() + { + var context = new TestableSubscribeContext(); + var state = context.Extensions.GetOrCreate(); + state.MaxRetries = 10; + state.RetryDelay = TimeSpan.Zero; + dispatcher.FailDispatch(10); + + await subscribeTerminator.Invoke(context, c => TaskEx.CompletedTask); + + Assert.AreEqual(1, dispatcher.DispatchedTransportOperations.Count); + Assert.AreEqual(10, dispatcher.FailedNumberOfTimes); + } + + [Test] + public void Should_Throw_when_max_retries_reached() + { + var context = new TestableSubscribeContext(); + var state = context.Extensions.GetOrCreate(); + state.MaxRetries = 10; + state.RetryDelay = TimeSpan.Zero; + dispatcher.FailDispatch(11); + + Assert.That(async () => + { + await subscribeTerminator.Invoke(context, c => TaskEx.CompletedTask); + }, Throws.InstanceOf()); + + Assert.AreEqual(0, dispatcher.DispatchedTransportOperations.Count); + Assert.AreEqual(11, dispatcher.FailedNumberOfTimes); + } + + FakeDispatcher dispatcher; + SubscriptionRouter router; + MessageDrivenSubscribeTerminator subscribeTerminator; + + class FakeDispatcher : IDispatchMessages + { + public int FailedNumberOfTimes { get; private set; } + + public List DispatchedTransportOperations { get; } = new List(); + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + if (numberOfTimes.HasValue && FailedNumberOfTimes < numberOfTimes.Value) + { + FailedNumberOfTimes++; + throw new QueueNotFoundException(); + } + + DispatchedTransportOperations.Add(outgoingMessages); + return TaskEx.CompletedTask; + } + + public void FailDispatch(int times) + { + numberOfTimes = times; + } + + int? numberOfTimes; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/AssemblyPublisherSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/AssemblyPublisherSourceTests.cs new file mode 100644 index 00000000000..41c88da7cb3 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/AssemblyPublisherSourceTests.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using System.Reflection; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using Settings; + + [TestFixture] + public class AssemblyPublisherSourceTests + { + [Test] + public void It_returns_only_event_types() + { + var source = new AssemblyPublisherSource(Assembly.GetExecutingAssembly(), PublisherAddress.CreateFromEndpointName("Destination")); + var routes = source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(); + + Assert.IsFalse(routes.Any(r => r.EventType == typeof(NonMessage))); + Assert.IsFalse(routes.Any(r => r.EventType == typeof(NonEvent))); + } + + + [Test] + public void It_throws_if_specified_assembly_contains_no_message_types() + { + var source = new AssemblyPublisherSource(typeof(string).Assembly, PublisherAddress.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("Cannot configure publisher for assembly")); + } + + [Test] + public void Without_best_practice_enforcement_it_throws_if_specified_assembly_contains_only_commands() + { + //Enforce artificial conventions to make sure the is a single command (but no messages) in an assembly + var conventionBuilder = new ConventionsBuilder(new SettingsHolder()); + conventionBuilder.DefiningCommandsAs(t => t == typeof(string)); + + + var source = new AssemblyPublisherSource(typeof(string).Assembly, PublisherAddress.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateWithoutBestPracticeEnforcement(conventionBuilder.Conventions).ToArray(), Throws.Exception.Message.Contains("Cannot configure publisher for assembly")); + } + + class NonMessage + { + } + + class NonEvent : IMessage + { + } + } +} diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensionsTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensionsTests.cs new file mode 100644 index 00000000000..e40e09d98df --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensionsTests.cs @@ -0,0 +1,206 @@ +namespace NServiceBus.Core.Tests.Routing.MessageDrivenSubscriptions +{ + using NServiceBus; + using System; + using System.Linq; + using System.Reflection; + using EventNamespace; + using MessageNameSpace; + using NServiceBus.Features; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using OtherMesagenameSpace; + using Settings; + using Transport; + + [TestFixture] + public class RoutingSettingsTests + { + [Test] + public void WhenPassingTransportAddressForPublisherInsteadOfEndpointName_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + + var exception = Assert.Throws(() => routingSettings.RegisterPublisher(typeof(Event), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessageForWrongEndpointName, exception.Message); + } + + [Test] + public void WhenPassingTransportAddressForPublisherInsteadOfEndpointName_UsingAssembly_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + + var exception = Assert.Throws(() => routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessageForWrongEndpointName, exception.Message); + } + + [Test] + public void WhenPassingTransportAddressForPublisherInsteadOfEndpointName_UsingAssemblyAndNamespace_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + + var exception = Assert.Throws(() => routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), nameof(EventNamespace), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessageForWrongEndpointName, exception.Message); + } + + [Test] + public void WhenPassingEndpointNameForPublisher_ShouldAddRouteToPublishers() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(typeof(Event), "EndpointName"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var publishersForEvent = publishers.GetPublisherFor(typeof(Event)).SingleOrDefault(); + Assert.IsNotNull(publishersForEvent); + } + + [Test] + public void WhenPassingEndpointNameForPublisher_UsingAssembly_ShouldAddAllEventsToPublishers() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), "EndpointName"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var publishersForEvent = publishers.GetPublisherFor(typeof(Event)).SingleOrDefault(); + var publishersForEventWithNamespace = publishers.GetPublisherFor(typeof(EventWithNamespace)).SingleOrDefault(); + + Assert.IsNotNull(publishersForEvent); + Assert.IsNotNull(publishersForEventWithNamespace); + } + + [Test] + public void WhenPassingEndpointNameForPublisher_UsingAssemblyAndNamespace_ShouldAddEventsWithNamespaceToPublishers() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), nameof(EventNamespace), "EndpointName"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var publishersForEvent = publishers.GetPublisherFor(typeof(Event)).SingleOrDefault(); + var publishersForEventWithNamespace = publishers.GetPublisherFor(typeof(EventWithNamespace)).SingleOrDefault(); + + Assert.IsNull(publishersForEvent); + Assert.IsNotNull(publishersForEventWithNamespace); + } + + [Test] + public void Should_register_all_types_in_assembly_when_not_specifying_namespace() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), "someAddress"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var result1 = publishers.GetPublisherFor(typeof(BaseMessage)).SingleOrDefault(); + var result2 = publishers.GetPublisherFor(typeof(SubMessage)).SingleOrDefault(); + var result3 = publishers.GetPublisherFor(typeof(EventWithoutNamespace)).SingleOrDefault(); + var result4 = publishers.GetPublisherFor(typeof(IMessageInterface)).SingleOrDefault(); + + Assert.IsNotNull(result1); + Assert.IsNotNull(result2); + Assert.IsNotNull(result3); + Assert.IsNotNull(result4); + } + + [Test] + public void Should_only_register_types_in_specified_namespace() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), "MessageNameSpace", "someAddress"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var result1 = publishers.GetPublisherFor(typeof(BaseMessage)).SingleOrDefault(); + var result2 = publishers.GetPublisherFor(typeof(SubMessage)).SingleOrDefault(); + var result3 = publishers.GetPublisherFor(typeof(EventWithoutNamespace)).SingleOrDefault(); + var result4 = publishers.GetPublisherFor(typeof(IMessageInterface)).SingleOrDefault(); + + Assert.IsNotNull(result1); + Assert.IsNotNull(result4); + Assert.IsNull(result2); + Assert.IsNull(result3); + } + + [Theory] + [TestCase("")] + [TestCase(null)] + public void Should_support_empty_namespace(string eventNamespace) + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RegisterPublisher(Assembly.GetExecutingAssembly(), eventNamespace, "someAddress"); + + var publishers = ApplyPublisherRegistrations(routingSettings); + + var result1 = publishers.GetPublisherFor(typeof(BaseMessage)).SingleOrDefault(); + var result2 = publishers.GetPublisherFor(typeof(SubMessage)).SingleOrDefault(); + var result3 = publishers.GetPublisherFor(typeof(EventWithoutNamespace)).SingleOrDefault(); + var result4 = publishers.GetPublisherFor(typeof(IMessageInterface)).SingleOrDefault(); + + Assert.IsNotNull(result3); + Assert.IsNull(result1); + Assert.IsNull(result2); + Assert.IsNull(result4); + } + + static Publishers ApplyPublisherRegistrations(RoutingSettings routingSettings) + { + var publishers = new Publishers(); + var registrations = routingSettings.Settings.Get(); + registrations.Apply(publishers, new Conventions(), true); + return publishers; + } + + const string expectedExceptionMessageForWrongEndpointName = "A logical endpoint name should not contain '@', but received 'EndpointName@MyHost'. To specify an endpoint's address, use the instance mapping file for the MSMQ transport, or refer to the routing documentation."; + } + + public class MessageDrivenTransportDefinition : TransportDefinition, IMessageDrivenSubscriptionTransport + { + public override string ExampleConnectionStringForErrorMessage { get; } + + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + throw new NotImplementedException(); + } + } +} + +namespace EventNamespace +{ + using NServiceBus; + class EventWithNamespace : IEvent + { + } +} + +class Event : NServiceBus.IEvent +{ +} + +namespace MessageNameSpace +{ + using NServiceBus; + + interface IMessageInterface : IEvent + { + } + + class BaseMessage : IMessageInterface + { + } +} + +namespace OtherMesagenameSpace +{ + using MessageNameSpace; + + class SubMessage : BaseMessage + { + } +} + +class EventWithoutNamespace : NServiceBus.IEvent +{ +} diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/NamespacePublisherSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/NamespacePublisherSourceTests.cs new file mode 100644 index 00000000000..b84adebb5a5 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/NamespacePublisherSourceTests.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using System.Reflection; + using NamespacePublisherSourceTest; + using NamespacePublisherSourceTest.OtherNamespace; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + + [TestFixture] + public class NamespacePublisherSourceTests + { + [Test] + public void It_returns_only_event_types() + { + var source = new NamespacePublisherSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest", PublisherAddress.CreateFromEndpointName("Destination")); + var routes = source.GenerateWithBestPracticeEnforcement(new Conventions()); + var routedTypes = routes.Select(r => r.EventType).ToArray(); + + CollectionAssert.Contains(routedTypes, typeof(Event)); + CollectionAssert.DoesNotContain(routedTypes, typeof(NonMessage)); + CollectionAssert.DoesNotContain(routedTypes, typeof(NonEvent)); + } + + [Test] + public void It_returns_only_types_from_specified_namespace() + { + var source = new NamespacePublisherSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest", PublisherAddress.CreateFromEndpointName("Destination")); + var routes = source.GenerateWithBestPracticeEnforcement(new Conventions()); + var routedTypes = routes.Select(r => r.EventType).ToArray(); + + CollectionAssert.Contains(routedTypes, typeof(Event)); + CollectionAssert.DoesNotContain(routedTypes, typeof(ExcludedEvent)); + } + + [Test] + public void It_matches_namespace_in_case_insensitive_way() + { + var source = new NamespacePublisherSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NAMESPACEpublisherSOURCEtest", PublisherAddress.CreateFromEndpointName("Destination")); + var routes = source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(); + var routedTypes = routes.Select(r => r.EventType); + + CollectionAssert.Contains(routedTypes, typeof(Event)); + } + + [Test] + public void It_throws_if_specified_namespace_contains_no_message_types() + { + var source = new NamespacePublisherSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest.NoMessages", PublisherAddress.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("Cannot configure publisher for namespace")); + } + + [Test] + public void Without_best_practice_enforcement_it_throws_if_specified_assembly_contains_only_commands() + { + var source = new NamespacePublisherSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest.Commands", PublisherAddress.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateWithoutBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("Cannot configure publisher for namespace")); + } + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest.Commands +{ + class Command : ICommand + { + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest.OtherNamespace +{ + class ExcludedEvent : IEvent + { + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest +{ + class Event : IEvent + { + } + + class NonMessage + { + } + + class NonEvent : IMessage + { + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespacePublisherSourceTest.NoMessages +{ + class NonMessage + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/PublishersTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/PublishersTests.cs new file mode 100644 index 00000000000..e9310312ce0 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/PublishersTests.cs @@ -0,0 +1,114 @@ +namespace NServiceBus.Core.Tests.Routing.MessageDrivenSubscriptions +{ + using System.Collections.Generic; + using System.Linq; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + + [TestFixture] + public class PublishersTests + { + [Test] + public void When_group_does_not_exist_should_add_routes() + { + var publisherTable = new Publishers(); + var publisher = PublisherAddress.CreateFromEndpointName("Endpoint1"); + publisherTable.AddOrReplacePublishers("key", new List + { + new PublisherTableEntry(typeof(MyEvent), publisher), + }); + + var retrievedPublisher = publisherTable.GetPublisherFor(typeof(MyEvent)).Single(); + Assert.AreSame(publisher, retrievedPublisher); + } + + [Test] + public void When_group_exists_should_replace_existing_routes() + { + var publisherTable = new Publishers(); + var oldPublisher = PublisherAddress.CreateFromEndpointName("Endpoint1"); + var newPublisher = PublisherAddress.CreateFromEndpointName("Endpoint2"); + publisherTable.AddOrReplacePublishers("key", new List + { + new PublisherTableEntry(typeof(MyEvent), oldPublisher), + }); + + publisherTable.AddOrReplacePublishers("key", new List + { + new PublisherTableEntry(typeof(MyEvent), newPublisher), + }); + + var retrievedPublisher = publisherTable.GetPublisherFor(typeof(MyEvent)).Single(); + Assert.AreSame(newPublisher, retrievedPublisher); + } + + [Test] + public void When_multiple_publishers_exist_should_return_all_of_them() + { + var publisherTable = new Publishers(); + + var pub1 = PublisherAddress.CreateFromEndpointName("Endpoint1"); + var pub2 = PublisherAddress.CreateFromEndpointName("Endpoint2"); + + publisherTable.AddOrReplacePublishers("key2", new List + { + new PublisherTableEntry(typeof(MyEvent), pub1), + }); + publisherTable.AddOrReplacePublishers("key1", new List + { + new PublisherTableEntry(typeof(MyEvent), pub2), + }); + + var pubs = publisherTable.GetPublisherFor(typeof(MyEvent)).ToArray(); + + Assert.Contains(pub1, pubs); + Assert.Contains(pub2, pubs); + } + + [Test] + public void When_same_publisher_is_registered_multiple_times_should_remove_duplicates() + { + var publisherTable = new Publishers(); + + var pub1 = PublisherAddress.CreateFromEndpointName("Endpoint1"); + var pub2 = PublisherAddress.CreateFromEndpointName("Endpoint1"); + var pub3 = PublisherAddress.CreateFromEndpointInstances(new EndpointInstance("Instance1"), new EndpointInstance("Instance2")); + var pub4 = PublisherAddress.CreateFromEndpointInstances(new EndpointInstance("Instance1"), new EndpointInstance("Instance2")); + var pub5 = PublisherAddress.CreateFromPhysicalAddresses("address1", "address2"); + var pub6 = PublisherAddress.CreateFromPhysicalAddresses("address1", "address2"); + + publisherTable.AddOrReplacePublishers("key2", new List + { + new PublisherTableEntry(typeof(MyEvent), pub1), + new PublisherTableEntry(typeof(MyEvent), pub2), + new PublisherTableEntry(typeof(MyEvent), pub3), + new PublisherTableEntry(typeof(MyEvent), pub4), + new PublisherTableEntry(typeof(MyEvent), pub5), + new PublisherTableEntry(typeof(MyEvent), pub6) + }); + + var pubs = publisherTable.GetPublisherFor(typeof(MyEvent)).ToArray(); + + Assert.AreEqual(3, pubs.Length); + Assert.Contains(pub1, pubs); + Assert.Contains(pub2, pubs); + Assert.Contains(pub3, pubs); + Assert.Contains(pub4, pubs); + Assert.Contains(pub5, pubs); + Assert.Contains(pub6, pubs); + } + + class MyEvent : IEvent + { + } + + class MyEvent2 : IEvent + { + } + + class MyEvent3 : IEvent + { + } + } +} diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/TypePublisherSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/TypePublisherSourceTests.cs new file mode 100644 index 00000000000..41531008e69 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenSubscriptions/TypePublisherSourceTests.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + + [TestFixture] + public class TypePublisherSourceTests + { + [Test] + public void It_throws_if_specified_type_is_not_a_message() + { + var source = new TypePublisherSource(typeof(NonMessage), PublisherAddress.CreateFromEndpointName("Destination")); + Assert.That(() => source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("it is not considered a message")); + } + + [Test] + public void It_throws_if_specified_type_is_not_an_event() + { + var source = new TypePublisherSource(typeof(NonEvent), PublisherAddress.CreateFromEndpointName("Destination")); + Assert.That(() => source.GenerateWithBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("it is not considered an event")); + } + + [Test] + public void Without_best_practivce_enforcement_it_throws_if_specified_type_is_not_a_message() + { + var source = new TypePublisherSource(typeof(NonMessage), PublisherAddress.CreateFromEndpointName("Destination")); + Assert.That(() => source.GenerateWithoutBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("it is not considered a message")); + } + + [Test] + public void Without_best_practivce_enforcement_it_throws_if_specified_type_is_a_command() + { + var source = new TypePublisherSource(typeof(Command), PublisherAddress.CreateFromEndpointName("Destination")); + Assert.That(() => source.GenerateWithoutBestPracticeEnforcement(new Conventions()).ToArray(), Throws.Exception.Message.Contains("because it is a command")); + } + + class NonMessage + { + } + + class NonEvent : IMessage + { + } + + class Command : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/MessageDrivenUnsubscribeTerminatorTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageDrivenUnsubscribeTerminatorTests.cs new file mode 100644 index 00000000000..fc153f7f207 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageDrivenUnsubscribeTerminatorTests.cs @@ -0,0 +1,105 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using Transport; + using Unicast.Queuing; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class MessageDrivenUnsubscribeTerminatorTests + { + MessageDrivenUnsubscribeTerminator terminator; + SubscriptionRouter router; + FakeDispatcher dispatcher; + + [SetUp] + public void SetUp() + { + var publishers = new Publishers(); + publishers.AddOrReplacePublishers("A", new List {new PublisherTableEntry(typeof(object), PublisherAddress.CreateFromPhysicalAddresses("publisher1"))}); + router = new SubscriptionRouter(publishers, new EndpointInstances(), i => i.ToString()); + dispatcher = new FakeDispatcher(); + terminator = new MessageDrivenUnsubscribeTerminator(router, "replyToAddress", "Endpoint", dispatcher); + } + + [Test] + public async Task Should_Dispatch_for_all_publishers() + { + await terminator.Invoke(new TestableUnsubscribeContext(), c => TaskEx.CompletedTask); + + Assert.AreEqual(1, dispatcher.DispatchedTransportOperations.Count); + } + + [Test] + public async Task Should_Dispatch_according_to_max_retries_when_dispatch_fails() + { + var options = new UnsubscribeOptions(); + var state = options.GetExtensions().GetOrCreate(); + state.MaxRetries = 10; + state.RetryDelay = TimeSpan.Zero; + dispatcher.FailDispatch(10); + + var context = new TestableUnsubscribeContext + { + Extensions = options.Context + }; + + await terminator.Invoke(context, c => TaskEx.CompletedTask); + + Assert.AreEqual(1, dispatcher.DispatchedTransportOperations.Count); + Assert.AreEqual(10, dispatcher.FailedNumberOfTimes); + } + + [Test] + public void Should_Throw_when_max_retries_reached() + { + var options = new UnsubscribeOptions(); + var state = options.GetExtensions().GetOrCreate(); + state.MaxRetries = 10; + state.RetryDelay = TimeSpan.Zero; + dispatcher.FailDispatch(11); + + var context = new TestableUnsubscribeContext + { + Extensions = options.Context + }; + + Assert.That(async () => await terminator.Invoke(context, c => TaskEx.CompletedTask), Throws.InstanceOf()); + + Assert.AreEqual(0, dispatcher.DispatchedTransportOperations.Count); + Assert.AreEqual(11, dispatcher.FailedNumberOfTimes); + } + + class FakeDispatcher : IDispatchMessages + { + int? numberOfTimes; + + public int FailedNumberOfTimes { get; private set; } = 0; + + public List DispatchedTransportOperations { get; } = new List(); + + public void FailDispatch(int times) + { + numberOfTimes = times; + } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + if (numberOfTimes.HasValue && FailedNumberOfTimes < numberOfTimes.Value) + { + FailedNumberOfTimes++; + throw new QueueNotFoundException(); + } + + DispatchedTransportOperations.Add(outgoingMessages); + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/MessageEndpointMappingTests.cs b/src/NServiceBus.Core.Tests/Routing/MessageEndpointMappingTests.cs new file mode 100644 index 00000000000..f79eb414c64 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/MessageEndpointMappingTests.cs @@ -0,0 +1,120 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Reflection; + using MessageNamespaceA; + using MessageNamespaceB; + using NServiceBus.Config; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + + [TestFixture] + public class MessageEndpointMappingTests + { + [Test] + public void WhenRoutingMessageTypeToEndpoint_ShouldConfigureMessageTypeInRoutingTable() + { + var mappings = new MessageEndpointMappingCollection + { + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + TypeFullName = typeof(SomeMessageType).FullName, + Endpoint = "destination" + } + }; + + var routingTable = ApplyMappings(mappings); + var route = routingTable.GetRouteFor(typeof(SomeMessageType)); + + Assert.That(route, Is.Not.Null); + } + + [Test] + public void WhenRoutingAssemblyToEndpoint_ShouldConfigureAllContainedMessagesInRoutingTable() + { + var mappings = new MessageEndpointMappingCollection + { + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + Endpoint = "destination" + } + }; + + var routingTable = ApplyMappings(mappings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + var messageWithoutNamespaceRoute = routingTable.GetRouteFor(typeof(MessageWithoutNamespace)); + + Assert.That(someMessageRoute, Is.Not.Null); + Assert.That(otherMessageRoute, Is.Not.Null); + Assert.That(messageWithoutNamespaceRoute, Is.Not.Null); + } + + [Test] + public void WhenRoutingAssemblyWithNamespaceToEndpoint_ShouldOnlyConfigureMessagesWithinThatNamespace() + { + var mappings = new MessageEndpointMappingCollection + { + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + Namespace = nameof(MessageNamespaceA), + Endpoint = "destination" + }, + }; + var routingTable = ApplyMappings(mappings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + var messageWithoutNamespaceRoute = routingTable.GetRouteFor(typeof(MessageWithoutNamespace)); + + Assert.That(someMessageRoute, Is.Not.Null, "because SomeMessageType is in the given namespace"); + Assert.That(otherMessageRoute, Is.Null, "because OtherMessageType is not in the given namespace"); + Assert.That(messageWithoutNamespaceRoute, Is.Null, "because MessageWithoutNamespace is not in the given namespace"); + } + + [Test] + public void WhenMultipleRoutesExist_ShouldUseMostSpecificOnes() + { + var mappings = new MessageEndpointMappingCollection + { + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + TypeFullName = typeof(SomeMessageType).FullName, + Endpoint = "type_destination" + }, + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + Namespace = nameof(MessageNamespaceA), + Endpoint = "namespace_destination" + }, + new MessageEndpointMapping + { + AssemblyName = Assembly.GetExecutingAssembly().FullName, + Endpoint = "assembly_destination" + } + }; + var routingTable = ApplyMappings(mappings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var yetAnotherRoute = routingTable.GetRouteFor(typeof(YetAnotherMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + + Assert.AreEqual("type_destination", someMessageRoute.PhysicalAddress); + Assert.AreEqual("namespace_destination", yetAnotherRoute.PhysicalAddress); + Assert.AreEqual("assembly_destination", otherMessageRoute.PhysicalAddress); + } + + static UnicastRoutingTable ApplyMappings(MessageEndpointMappingCollection mappings) + { + var routeTable = new UnicastRoutingTable(); + mappings.Apply(new Publishers(), routeTable, x => x, new Conventions()); + return routeTable; + } + } +} diff --git a/src/NServiceBus.Core.Tests/Routing/NamespaceRouteSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/NamespaceRouteSourceTests.cs new file mode 100644 index 00000000000..c080f55f237 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/NamespaceRouteSourceTests.cs @@ -0,0 +1,75 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using System.Reflection; + using NamespaceRouteSourceTest; + using NamespaceRouteSourceTest.OtherNamespace; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class NamespaceRouteSourceTests + { + [Test] + public void It_returns_only_message_types() + { + var source = new NamespaceRouteSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest", UnicastRoute.CreateFromEndpointName("Destination")); + var routes = source.GenerateRoutes(new Conventions()).ToArray(); + + Assert.IsTrue(routes.Any(r => r.MessageType == typeof(Message))); + Assert.IsFalse(routes.Any(r => r.MessageType == typeof(NonMessage))); + } + + [Test] + public void It_returns_only_types_from_specified_namespace() + { + var source = new NamespaceRouteSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest", UnicastRoute.CreateFromEndpointName("Destination")); + var routes = source.GenerateRoutes(new Conventions()).ToArray(); + + Assert.IsTrue(routes.Any(r => r.MessageType == typeof(Message))); + Assert.IsFalse(routes.Any(r => r.MessageType == typeof(ExcludedMessage))); + } + + [Test] + public void It_matches_namespace_in_case_insensitive_way() + { + var source = new NamespaceRouteSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NAMESPACErouteSOURCEtest", UnicastRoute.CreateFromEndpointName("Destination")); + var routes = source.GenerateRoutes(new Conventions()).ToArray(); + + Assert.IsTrue(routes.Any(r => r.MessageType == typeof(Message))); + } + + [Test] + public void It_throws_if_specified_namespace_contains_no_message_types() + { + var source = new NamespaceRouteSource(Assembly.GetExecutingAssembly(), "NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest.NoMessages", UnicastRoute.CreateFromEndpointName("Destination")); + + Assert.That(() => source.GenerateRoutes(new Conventions()).ToArray(), Throws.Exception.Message.Contains("Cannot configure routing for namespace")); + } + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest.OtherNamespace +{ + class ExcludedMessage : IMessage + { + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest +{ + class Message : IMessage + { + } + + class NonMessage + { + } +} + +namespace NServiceBus.Core.Tests.Routing.NamespaceRouteSourceTest.NoMessages +{ + class NonMessage + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/Routers/MulticastPublishRouterBehaviorTests.cs b/src/NServiceBus.Core.Tests/Routing/Routers/MulticastPublishRouterBehaviorTests.cs new file mode 100644 index 00000000000..c252e20b47b --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/Routers/MulticastPublishRouterBehaviorTests.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.Core.Tests.Routing.Routers +{ + using System.Threading.Tasks; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class MulticastPublishRouterBehaviorTests + { + [Test] + public async Task Should_set_messageintent_to_publish() + { + var router = new MulticastPublishRouterBehavior(); + var context = new TestableOutgoingPublishContext(); + + await router.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(1, context.Headers.Count); + Assert.AreEqual(MessageIntentEnum.Publish.ToString(), context.Headers[Headers.MessageIntent]); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/Routers/UnicastPublishRouterConnectorTests.cs b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastPublishRouterConnectorTests.cs new file mode 100644 index 00000000000..483ef36553e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastPublishRouterConnectorTests.cs @@ -0,0 +1,38 @@ +namespace NServiceBus.Core.Tests.Routing.Routers +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class UnicastPublishRouterConnectorTests + { + [Test] + public async Task Should_set_messageintent_to_publish() + { + var router = new UnicastPublishRouterConnector(new FakePublishRouter(), new DistributionPolicy()); + var context = new TestableOutgoingPublishContext(); + + await router.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(1, context.Headers.Count); + Assert.AreEqual(MessageIntentEnum.Publish.ToString(), context.Headers[Headers.MessageIntent]); + } + + class FakePublishRouter : IUnicastPublishRouter + { + public Task> Route(Type messageType, IDistributionPolicy distributionPolicy, ContextBag contextBag) + { + IEnumerable unicastRoutingStrategies = new List + { + new UnicastRoutingStrategy("Fake") + }; + return Task.FromResult(unicastRoutingStrategies); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/Routers/UnicastReplyRouterConnectorTests.cs b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastReplyRouterConnectorTests.cs new file mode 100644 index 00000000000..b5a84c93f5c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastReplyRouterConnectorTests.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Core.Tests.Routing.Routers +{ + using System.Threading.Tasks; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class UnicastReplyRouterConnectorTests + { + [Test] + public async Task Should_set_messageintent_to_reply() + { + var router = new UnicastReplyRouterConnector(); + var context = new TestableOutgoingReplyContext(); + context.Extensions.Set(new UnicastReplyRouterConnector.State { ExplicitDestination = "Fake" }); + + await router.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(1, context.Headers.Count); + Assert.AreEqual(MessageIntentEnum.Reply.ToString(), context.Headers[Headers.MessageIntent]); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/Routers/UnicastSendRouterConnectorTests.cs b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastSendRouterConnectorTests.cs new file mode 100644 index 00000000000..099519bc87d --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/Routers/UnicastSendRouterConnectorTests.cs @@ -0,0 +1,248 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NServiceBus.Routing; + using Unicast.Messages; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class UnicastSendRouterConnectorTests + { + [Test] + public async Task Should_set_messageintent_to_send() + { + var behavior = InitializeBehavior(); + var options = new SendOptions(); + options.SetDestination("destination endpoint"); + var context = CreateContext(options); + + await behavior.Invoke(context, ctx => TaskEx.CompletedTask); + + Assert.AreEqual(1, context.Headers.Count); + Assert.AreEqual(MessageIntentEnum.Send.ToString(), context.Headers[Headers.MessageIntent]); + } + + [Test] + public async Task Should_use_explicit_route_for_sends_if_present() + { + var behavior = InitializeBehavior(); + var options = new SendOptions(); + + options.SetDestination("destination endpoint"); + + var context = CreateContext(options); + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag)c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("destination endpoint", addressTag.Destination); + } + + [Test] + public async Task Should_route_to_local_endpoint_if_requested_so() + { + var behavior = InitializeBehavior(sharedQueue: "MyLocalAddress"); + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + var context = CreateContext(options); + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag)c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("MyLocalAddress", addressTag.Destination); + } + + [Test] + public async Task Should_route_to_local_instance_if_requested_so() + { + var behavior = InitializeBehavior(sharedQueue: "MyLocalAddress", instanceSpecificQueue: "MyInstance"); + var options = new SendOptions(); + + options.RouteToThisInstance(); + + var context = CreateContext(options); + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag)c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("MyInstance", addressTag.Destination); + } + + [Test] + public async Task Should_throw_if_requested_to_route_to_local_instance_and_instance_has_no_specific_queue() + { + var behavior = InitializeBehavior(sharedQueue: "MyLocalAddress", instanceSpecificQueue: null); + + try + { + var options = new SendOptions(); + + options.RouteToThisInstance(); + + var context = CreateContext(options); + await behavior.Invoke(context, c => TaskEx.CompletedTask); + Assert.Fail("RouteToThisInstance"); + } + catch (Exception) + { + // ignored + } + } + + [Test] + public async Task Should_throw_if_invalid_route_combinations_are_used() + { + var behavior = InitializeBehavior(sharedQueue: "MyLocalAddress", instanceSpecificQueue: "MyInstance"); + + try + { + var options = new SendOptions(); + + options.RouteToThisInstance(); + options.SetDestination("Destination"); + + var context = CreateContext(options); + await behavior.Invoke(context, c => TaskEx.CompletedTask); + Assert.Fail("RouteToThisInstance+SetDestination"); + } + catch (Exception) + { + // ignored + } + + try + { + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + options.SetDestination("Destination"); + + var context = CreateContext(options); + await behavior.Invoke(context, c => TaskEx.CompletedTask); + Assert.Fail("RouteToThisEndpoint+SetDestination"); + } + catch (Exception) + { + // ignored + } + + try + { + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + options.RouteToThisInstance(); + + var context = CreateContext(options); + await behavior.Invoke(context, c => TaskEx.CompletedTask); + Assert.Fail("RouteToThisEndpoint+RouteToThisInstance"); + } + catch (Exception) + { + // ignored + } + } + + [Test] + public async Task Should_route_using_the_mappings_if_no_destination_is_set() + { + var router = new FakeSendRouter + { + FixedDestination = new UnicastRoutingStrategy("MappedDestination") + }; + var behavior = InitializeBehavior(router: router); + var options = new SendOptions(); + + var context = CreateContext(options); + + UnicastAddressTag addressTag = null; + await behavior.Invoke(context, c => + { + addressTag = (UnicastAddressTag)c.RoutingStrategies.Single().Apply(new Dictionary()); + return TaskEx.CompletedTask; + }); + + Assert.AreEqual("MappedDestination", addressTag.Destination); + } + + [Test] + public void Should_throw_if_no_route_can_be_found() + { + var router = new FakeSendRouter + { + FixedDestination = null + }; + + var behavior = InitializeBehavior(router: router); + var options = new SendOptions(); + + var context = CreateContext(options, new MessageWithoutRouting()); + + Assert.That(async () => await behavior.Invoke(context, _ => TaskEx.CompletedTask), Throws.InstanceOf().And.Message.Contains("No destination specified")); + } + + static IOutgoingSendContext CreateContext(SendOptions options, object message = null) + { + if (message == null) + { + message = new MyMessage(); + } + + var context = new TestableOutgoingSendContext + { + Message = new OutgoingLogicalMessage(message.GetType(), message), + Extensions = options.Context + }; + return context; + } + + + static UnicastSendRouterConnector InitializeBehavior( + string sharedQueue = null, + string instanceSpecificQueue = null, + FakeSendRouter router = null) + { + var metadataRegistry = new MessageMetadataRegistry(new Conventions()); + metadataRegistry.RegisterMessageTypesFoundIn(new List { typeof(MyMessage), typeof(MessageWithoutRouting) }); + + return new UnicastSendRouterConnector(sharedQueue, instanceSpecificQueue, router ?? new FakeSendRouter(), new DistributionPolicy(), e => e.ToString()); + } + + class FakeSendRouter : IUnicastSendRouter + { + public UnicastRoutingStrategy FixedDestination { get; set; } + + public UnicastRoutingStrategy Route(Type messageType, IDistributionPolicy distributionPolicy) + { + return FixedDestination; + } + } + + class MyMessage + { + } + + class MessageWithoutRouting + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/RoutingOptionExtensionsTests.cs b/src/NServiceBus.Core.Tests/Routing/RoutingOptionExtensionsTests.cs new file mode 100644 index 00000000000..c5faaf0e441 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/RoutingOptionExtensionsTests.cs @@ -0,0 +1,217 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using NUnit.Framework; + + [TestFixture] + public class RoutingOptionExtensionsTests + { + [Test] + public void ReplyOptions_GetDestination_Should_Return_Configured_Destination() + { + const string expectedDestination = "custom reply destination"; + var options = new ReplyOptions(); + options.SetDestination(expectedDestination); + + var destination = options.GetDestination(); + + Assert.AreEqual(expectedDestination, destination); + } + + [Test] + public void ReplyOptions_GetDestination_Should_Return_Null_When_No_Destination_Configured() + { + var options = new ReplyOptions(); + + var destination = options.GetDestination(); + + Assert.IsNull(destination); + } + + [Test] + public void SendOptions_GetDestination_Should_Return_Configured_Destination() + { + const string expectedDestination = "custom send destination"; + var options = new SendOptions(); + options.SetDestination(expectedDestination); + + var destination = options.GetDestination(); + + Assert.AreEqual(expectedDestination, destination); + } + + [Test] + public void SendOptions_GetDestination_Should_Return_Null_When_No_Destination_Configured() + { + var options = new SendOptions(); + + var destination = options.GetDestination(); + + Assert.IsNull(destination); + } + + [Test] + public void IsRoutingToThisEndpoint_Should_Return_False_When_Not_Routed_To_This_Endpoint() + { + var options = new SendOptions(); + + Assert.IsFalse(options.IsRoutingToThisEndpoint()); + } + + [Test] + public void IsRoutingToThisEndpoint_Should_Return_True_When_Routed_To_This_Endpoint() + { + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + Assert.IsTrue(options.IsRoutingToThisEndpoint()); + } + + [Test] + public void IsRoutingToThisInstance_Should_Return_False_When_Not_Routed_To_This_Instance() + { + var options = new SendOptions(); + + Assert.IsFalse(options.IsRoutingToThisInstance()); + } + + [Test] + public void IsRoutingToThisInstance_Should_Return_True_When_Routed_To_This_Instance() + { + var options = new SendOptions(); + + options.RouteToThisInstance(); + + Assert.IsTrue(options.IsRoutingToThisInstance()); + } + + [Test] + public void GetRouteToSpecificInstance_Should_Return_Configured_Instance() + { + const string expectedInstanceId = "custom instance id"; + var options = new SendOptions(); + + options.RouteToSpecificInstance(expectedInstanceId); + + Assert.AreEqual(expectedInstanceId, options.GetRouteToSpecificInstance()); + } + + [Test] + public void GetRouteToSpecificInstance_Should_Return_Null_When_No_Instance_Configured() + { + var options = new SendOptions(); + + Assert.IsNull(options.GetRouteToSpecificInstance()); + } + + [Test] + public void SendOptions_IsRoutingReplyToThisInstance_Should_Return_True_When_Routing_Reply_To_This_Instance() + { + var options = new SendOptions(); + + options.RouteReplyToThisInstance(); + + Assert.IsTrue(options.IsRoutingReplyToThisInstance()); + } + + [Test] + public void SendOptions_IsRoutingReplyToThisInstance_Should_Return_False_When_Not_Routing_Reply_To_This_Instance() + { + var options = new SendOptions(); + + Assert.IsFalse(options.IsRoutingReplyToThisInstance()); + } + + [Test] + public void ReplyOptions_IsRoutingReplyToThisInstance_Should_Return_True_When_Routing_Reply_To_This_Instance() + { + var options = new ReplyOptions(); + + options.RouteReplyToThisInstance(); + + Assert.IsTrue(options.IsRoutingReplyToThisInstance()); + } + + [Test] + public void ReplyOptions_IsRoutingReplyToThisInstance_Should_Return_False_When_Not_Routing_Reply_To_This_Instance() + { + var options = new ReplyOptions(); + + Assert.IsFalse(options.IsRoutingReplyToThisInstance()); + } + + [Test] + public void SendOptions_IsRoutingReplyToAnyInstance_Should_Return_True_When_Routing_Reply_To_Any_Instance() + { + var options = new SendOptions(); + + options.RouteReplyToAnyInstance(); + + Assert.IsTrue(options.IsRoutingReplyToAnyInstance()); + } + + [Test] + public void SendOptions_IsRoutingReplyToAnyInstance_Should_Return_False_When_Not_Routing_Reply_To_Any_Instance() + { + var options = new SendOptions(); + + Assert.IsFalse(options.IsRoutingReplyToAnyInstance()); + } + + [Test] + public void ReplyOptions_IsRoutingReplyToAnyInstance_Should_Return_True_When_Routing_Reply_To_Any_Instance() + { + var options = new ReplyOptions(); + + options.RouteReplyToAnyInstance(); + + Assert.IsTrue(options.IsRoutingReplyToAnyInstance()); + } + + [Test] + public void ReplyOptions_IsRoutingReplyToAnyInstance_Should_Return_False_When_Not_Routing_Reply_To_Any_Instance() + { + var options = new ReplyOptions(); + + Assert.IsFalse(options.IsRoutingReplyToAnyInstance()); + } + + [Test] + public void ReplyOptions_GetReplyToRoute_Should_Return_Configured_Reply_Route() + { + const string expectedReplyToAddress = "custom replyTo address"; + var options = new ReplyOptions(); + + options.RouteReplyTo(expectedReplyToAddress); + + Assert.AreEqual(expectedReplyToAddress, options.GetReplyToRoute()); + } + + [Test] + public void ReplyOptions_GetReplyToRoute_Should_Return_Null_When_No_Route_Configured() + { + var options = new ReplyOptions(); + + Assert.IsNull(options.GetReplyToRoute()); + } + + [Test] + public void SendOptions_GetReplyToRoute_Should_Return_Configured_Reply_Route() + { + const string expectedReplyToAddress = "custom replyTo address"; + var options = new SendOptions(); + + options.RouteReplyTo(expectedReplyToAddress); + + Assert.AreEqual(expectedReplyToAddress, options.GetReplyToRoute()); + } + + [Test] + public void SendOptions_GetReplyToRoute_Should_Return_Null_When_No_Route_Configured() + { + var options = new SendOptions(); + + Assert.IsNull(options.GetReplyToRoute()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/RoutingPolicyTests.cs b/src/NServiceBus.Core.Tests/Routing/RoutingPolicyTests.cs new file mode 100644 index 00000000000..3131e9740c8 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/RoutingPolicyTests.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using NUnit.Framework; + + [TestFixture] + public class RoutingPolicyTests + { + [Test] + public void ShouldScopeDistributionToEndpointName() + { + var endpointA = "endpointA"; + var policy = new DistributionPolicy(); + var endpointAInstances = new[] + { + endpointA + "1", + endpointA + "2" + }; + + var endpointB = "endpointB"; + var endpointBInstances = new[] + { + endpointB + "1", + endpointB + "2" + }; + + var result = new List(); + result.Add(InvokeDistributionStrategy(policy, endpointA, endpointAInstances)); + result.Add(InvokeDistributionStrategy(policy, endpointB, endpointBInstances)); + result.Add(InvokeDistributionStrategy(policy, endpointA, endpointAInstances)); + result.Add(InvokeDistributionStrategy(policy, endpointB, endpointBInstances)); + + Assert.That(result.Count, Is.EqualTo(4)); + Assert.That(result, Has.Exactly(1).EqualTo(endpointAInstances[0])); + Assert.That(result, Has.Exactly(1).EqualTo(endpointAInstances[1])); + Assert.That(result, Has.Exactly(1).EqualTo(endpointBInstances[0])); + Assert.That(result, Has.Exactly(1).EqualTo(endpointBInstances[1])); + } + + static string InvokeDistributionStrategy(IDistributionPolicy policy, string endpointName, string[] instanceAddress) + { + return policy.GetDistributionStrategy(endpointName, DistributionStrategyScope.Send).SelectReceiver(instanceAddress); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/RoutingSettingsTests.cs b/src/NServiceBus.Core.Tests/Routing/RoutingSettingsTests.cs new file mode 100644 index 00000000000..076a21c712e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/RoutingSettingsTests.cs @@ -0,0 +1,146 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using NServiceBus; + using System; + using System.Reflection; + using MessageNamespaceA; + using MessageNamespaceB; + using NServiceBus.Features; + using NServiceBus.Routing; + using NUnit.Framework; + using Settings; + + [TestFixture] + public class RoutingSettingsTests + { + [Test] + public void WhenPassingTransportAddressForSenderInsteadOfEndpointName_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + var expectedExceptionMessage = expectedExceptionMessageForWrongEndpointName; + + var exception = Assert.Throws(() => routingSettings.RouteToEndpoint(typeof(MessageWithoutNamespace), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessage, exception.Message); + } + + [Test] + public void WhenPassingTransportAddressForSenderInsteadOfEndpointName_UsingAssembly_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + var expectedExceptionMessage = expectedExceptionMessageForWrongEndpointName; + + var exception = Assert.Throws(() => routingSettings.RouteToEndpoint(Assembly.GetExecutingAssembly(), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessage, exception.Message); + } + + [Test] + public void WhenPassingTransportAddressForSenderInsteadOfEndpointName_UsingAssemblyAndNamespace_ShouldThrowException() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + var expectedExceptionMessage = expectedExceptionMessageForWrongEndpointName; + + var exception = Assert.Throws(() => routingSettings.RouteToEndpoint(Assembly.GetExecutingAssembly(), nameof(MessageNamespaceA), "EndpointName@MyHost")); + Assert.AreEqual(expectedExceptionMessage, exception.Message); + } + + [Test] + public void WhenRoutingMessageTypeToEndpoint_ShouldConfigureMessageTypeInRoutingTable() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RouteToEndpoint(typeof(SomeMessageType), "destination"); + + var routingTable = ApplyConfiguredRoutes(routingSettings); + var route = routingTable.GetRouteFor(typeof(SomeMessageType)); + + Assert.That(route, Is.Not.Null); + Assert.That(route.Endpoint, Is.EqualTo("destination")); + } + + [Test] + public void WhenRoutingAssemblyToEndpoint_ShouldConfigureAllContainedMessagesInRoutingTable() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RouteToEndpoint(Assembly.GetExecutingAssembly(), "destination"); + + var routingTable = ApplyConfiguredRoutes(routingSettings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + var messageWithoutNamespaceRoute = routingTable.GetRouteFor(typeof(MessageWithoutNamespace)); + + Assert.That(someMessageRoute, Is.Not.Null); + Assert.That(otherMessageRoute, Is.Not.Null); + Assert.That(messageWithoutNamespaceRoute, Is.Not.Null); + } + + [Test] + public void WhenRoutingAssemblyWithNamespaceToEndpoint_ShouldOnlyConfigureMessagesWithinThatNamespace() + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RouteToEndpoint(Assembly.GetExecutingAssembly(), nameof(MessageNamespaceA), "destination"); + + var routingTable = ApplyConfiguredRoutes(routingSettings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + var messageWithoutNamespaceRoute = routingTable.GetRouteFor(typeof(MessageWithoutNamespace)); + + Assert.That(someMessageRoute, Is.Not.Null, "because SomeMessageType is in the given namespace"); + Assert.That(otherMessageRoute, Is.Null, "because OtherMessageType is not in the given namespace"); + Assert.That(messageWithoutNamespaceRoute, Is.Null, "because MessageWithoutNamespace is not in the given namespace"); + } + + [Theory] + [TestCase(null)] + [TestCase("")] + public void WhenRoutingAssemblyWithNamespaceToEndpointAndSpecifyingEmptyNamespace_ShouldOnlyConfigureMessagesWithinEmptyNamespace(string emptyNamespace) + { + var routingSettings = new RoutingSettings(new SettingsHolder()); + routingSettings.RouteToEndpoint(Assembly.GetExecutingAssembly(), emptyNamespace, "destination"); + + var routingTable = ApplyConfiguredRoutes(routingSettings); + + var someMessageRoute = routingTable.GetRouteFor(typeof(SomeMessageType)); + var otherMessageRoute = routingTable.GetRouteFor(typeof(OtherMessageType)); + var messageWithoutNamespaceRoute = routingTable.GetRouteFor(typeof(MessageWithoutNamespace)); + + Assert.That(someMessageRoute, Is.Null); + Assert.That(otherMessageRoute, Is.Null); + Assert.That(messageWithoutNamespaceRoute, Is.Not.Null); + } + + static UnicastRoutingTable ApplyConfiguredRoutes(RoutingSettings routingSettings) + { + var routingTable = new UnicastRoutingTable(); + var configuredRoutes = routingSettings.Settings.GetOrDefault(); + configuredRoutes?.Apply(routingTable, new Conventions()); + return routingTable; + } + + string expectedExceptionMessageForWrongEndpointName = "A logical endpoint name should not contain '@', but received 'EndpointName@MyHost'. To specify an endpoint's address, use the instance mapping file for the MSMQ transport, or refer to the routing documentation."; + } +} + +namespace MessageNamespaceA +{ + using NServiceBus; + class SomeMessageType : IMessage + { + } + + class YetAnotherMessageType : IMessage + { + } +} + +namespace MessageNamespaceB +{ + using NServiceBus; + class OtherMessageType : IMessage + { + } +} + +class MessageWithoutNamespace : NServiceBus.IMessage +{ +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/RoutingToDispatchConnectorTests.cs b/src/NServiceBus.Core.Tests/Routing/RoutingToDispatchConnectorTests.cs new file mode 100644 index 00000000000..83d98516373 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/RoutingToDispatchConnectorTests.cs @@ -0,0 +1,107 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using NServiceBus.Pipeline; + using NServiceBus.Routing; + using Transport; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class RoutingToDispatchConnectorTests + { + [Test] + public async Task Should_preserve_headers_generated_by_custom_routing_strategy() + { + var behavior = new RoutingToDispatchConnector(); + Dictionary headers = null; + await behavior.Invoke(new TestableRoutingContext { RoutingStrategies = new List { new CustomRoutingStrategy() } }, context => + { + headers = context.Operations.First().Message.Headers; + return TaskEx.CompletedTask; + }); + + Assert.IsTrue(headers.ContainsKey("CustomHeader")); + } + + [Test] + public async Task Should_dispatch_immediately_if_user_requested() + { + var options = new SendOptions(); + options.RequireImmediateDispatch(); + + var dispatched = false; + var behavior = new RoutingToDispatchConnector(); + var message = new OutgoingMessage("ID", new Dictionary(), new byte[0]); + + await behavior.Invoke(new RoutingContext(message, + new UnicastRoutingStrategy("Destination"), CreateContext(options, true)), c => + { + dispatched = true; + return TaskEx.CompletedTask; + }); + + Assert.IsTrue(dispatched); + } + + [Test] + public async Task Should_dispatch_immediately_if_not_sending_from_a_handler() + { + var dispatched = false; + var behavior = new RoutingToDispatchConnector(); + var message = new OutgoingMessage("ID", new Dictionary(), new byte[0]); + + await behavior.Invoke(new RoutingContext(message, + new UnicastRoutingStrategy("Destination"), CreateContext(new SendOptions(), false)), c => + { + dispatched = true; + return TaskEx.CompletedTask; + }); + + Assert.IsTrue(dispatched); + } + + [Test] + public async Task Should_not_dispatch_by_default() + { + var dispatched = false; + var behavior = new RoutingToDispatchConnector(); + var message = new OutgoingMessage("ID", new Dictionary(), new byte[0]); + + await behavior.Invoke(new RoutingContext(message, + new UnicastRoutingStrategy("Destination"), CreateContext(new SendOptions(), true)), c => + { + dispatched = true; + return TaskEx.CompletedTask; + }); + + Assert.IsFalse(dispatched); + } + + static IOutgoingSendContext CreateContext(SendOptions options, bool fromHandler) + { + var message = new MyMessage(); + var context = new OutgoingSendContext(new OutgoingLogicalMessage(message.GetType(), message), options, new RootContext(null, null, null)); + if (fromHandler) + { + context.Extensions.Set(new PendingTransportOperations()); + } + return context; + } + + class CustomRoutingStrategy : RoutingStrategy + { + public override AddressTag Apply(Dictionary headers) + { + headers["CustomHeader"] = "CustomValue"; + return new UnicastAddressTag("destination"); + } + } + + class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/SingleInstanceRoundRobinDistributionStrategyTests.cs b/src/NServiceBus.Core.Tests/Routing/SingleInstanceRoundRobinDistributionStrategyTests.cs new file mode 100644 index 00000000000..d8f1b8d218e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/SingleInstanceRoundRobinDistributionStrategyTests.cs @@ -0,0 +1,101 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using System.Linq; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class SingleInstanceRoundRobinDistributionStrategyTests + { + [Test] + public void ShouldRoundRobinOverAllProvidedInstances() + { + var strategy = new SingleInstanceRoundRobinDistributionStrategy("endpointA", DistributionStrategyScope.Send); + + var instances = new[] + { + "1", + "2", + "3" + }; + + var result = new List(); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result, Has.Exactly(1).EqualTo(instances[0])); + Assert.That(result, Has.Exactly(1).EqualTo(instances[1])); + Assert.That(result, Has.Exactly(1).EqualTo(instances[2])); + } + + [Test] + public void ShouldRestartAtFirstInstance() + { + var strategy = new SingleInstanceRoundRobinDistributionStrategy("endpointA", DistributionStrategyScope.Send); + + var instances = new[] + { + "1", + "2", + "3" + }; + + var result = new List(); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + + Assert.That(result.Last(), Is.EqualTo(result.First())); + } + + [Test] + public void WhenNewInstancesAdded_ShouldIncludeAllInstancesInDistribution() + { + var strategy = new SingleInstanceRoundRobinDistributionStrategy("endpointA", DistributionStrategyScope.Send); + + var instances = new [] + { + "1", + "2", + }; + + var result = new List(); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + instances = instances.Concat(new [] { "3" }).ToArray(); // add new instance + result.Add(strategy.SelectReceiver(instances)); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result, Has.Exactly(1).EqualTo(instances[0])); + Assert.That(result, Has.Exactly(1).EqualTo(instances[1])); + Assert.That(result, Has.Exactly(1).EqualTo(instances[2])); + } + + [Test] + public void WhenInstancesRemoved_ShouldOnlyDistributeAcrossRemainingInstances() + { + var strategy = new SingleInstanceRoundRobinDistributionStrategy("endpointA", DistributionStrategyScope.Send); + + var instances = new [] + { + "1", + "2", + "3" + }; + + var result = new List(); + result.Add(strategy.SelectReceiver(instances)); + result.Add(strategy.SelectReceiver(instances)); + instances = instances.Take(2).ToArray(); // remove last instance. + result.Add(strategy.SelectReceiver(instances)); + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result, Has.Exactly(2).EqualTo(instances[0])); + Assert.That(result, Has.Exactly(1).EqualTo(instances[1])); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/StaticMessageRouterTests.cs b/src/NServiceBus.Core.Tests/Routing/StaticMessageRouterTests.cs deleted file mode 100644 index 4ed74ba9dac..00000000000 --- a/src/NServiceBus.Core.Tests/Routing/StaticMessageRouterTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -namespace NServiceBus.Core.Tests.Sagas -{ - using System; - using System.Linq; - using NUnit.Framework; - using Unicast.Routing; - - [TestFixture] - public class StaticMessageRouterTests - { - [Test] - public void When_initialized_known_message_returns_empty() - { - var router = new StaticMessageRouter(new[] - { - typeof(Message1) - }); - Assert.IsEmpty(router.GetDestinationFor(typeof(Message1))); - } - - [Test] - public void When_route_with_undefined_address_is_registered_exception_is_thrown() - { - var router = new StaticMessageRouter(new Type[0]); - - Assert.Throws(() => router.RegisterMessageRoute(typeof(Message1), Address.Undefined)); - } - - [Test] - public void Test_that_expose_the_issue_with_the_current_codebase_assuming_that_routes_can_be_updated() - { - var router = new StaticMessageRouter(new Type[0]); - - var overrideAddress = Address.Parse("override"); - - router.RegisterMessageRoute(typeof(Message1), Address.Parse("first")); - router.RegisterMessageRoute(typeof(Message1), overrideAddress); - - Assert.AreEqual(overrideAddress, router.GetDestinationFor(typeof(Message1)).Single()); - } - - [Test] - public void When_initialized_unknown_message_returns_empty() - { - var router = new StaticMessageRouter(new[] - { - typeof(Message1) - }); - Assert.IsEmpty(router.GetDestinationFor(typeof(Message2))); - } - - [Test] - public void When_getting_route_correct_address_is_returned() - { - var router = new StaticMessageRouter(new[] - { - typeof(Message1) - }); - var address = new Address("a", "b"); - router.RegisterMessageRoute(typeof(Message1), address); - Assert.AreSame(address, router.GetDestinationFor(typeof(Message1)).Single()); - } - - [Test] - [Ignore("Will pass when we add support for this in v 4.2")] - public void When_inherited_registered_after_base_correct_address_is_returned_for_both() - { - var baseType = typeof(BaseMessage); - var inheritedType = typeof(InheritedMessage); - var router = new StaticMessageRouter(new[] - { - baseType, - inheritedType - }); - var baseAddress = new Address("baseAddress", "b"); - router.RegisterMessageRoute(baseType, baseAddress); - var inheritedAddress = new Address("inheritedAddress", "b"); - router.RegisterMessageRoute(inheritedType, inheritedAddress); - Assert.Contains(baseAddress, router.GetDestinationFor(baseType)); - Assert.Contains(inheritedAddress, router.GetDestinationFor(baseType)); - Assert.AreSame(inheritedAddress, router.GetDestinationFor(inheritedType).Single()); - } - - [Test] - public void When_inherited_base_after_registered_correct_address_is_returned_for_both() - { - var baseType = typeof(BaseMessage); - var inheritedType = typeof(InheritedMessage); - var router = new StaticMessageRouter(new[] - { - baseType, - inheritedType - }); - var inheritedAddress = new Address("inheritedAddress", "b"); - router.RegisterEventRoute(inheritedType, inheritedAddress); - var baseAddress = new Address("baseAddress", "b"); - router.RegisterEventRoute(baseType, baseAddress); - Assert.Contains(baseAddress, router.GetDestinationFor(baseType)); - Assert.Contains(inheritedAddress, router.GetDestinationFor(baseType)); - Assert.AreSame(inheritedAddress, router.GetDestinationFor(inheritedType).Single()); - } - - [Test] - public void When_registered_registering_multiple_addresses_for_same_type_same_number_of_addresses_are_returned() - { - var baseType = typeof(BaseMessage); - var router = new StaticMessageRouter(Enumerable.Empty()); - var addressA = new Address("BaseMessage", "A"); - router.RegisterEventRoute(baseType, addressA); - var addressB = new Address("BaseMessage", "b"); - router.RegisterEventRoute(baseType, addressB); - - Assert.AreEqual(2, router.GetDestinationFor(baseType).Count); - } - - [Test] - public void When_registered_registering_multiple_addresses_for_same_type_and_using_plainmessages_last_one_wins() - { - var baseType = typeof(BaseMessage); - var router = new StaticMessageRouter(Enumerable.Empty()); - var addressA = new Address("BaseMessage", "A"); - router.RegisterMessageRoute(baseType, addressA); - var addressB = new Address("BaseMessage", "b"); - router.RegisterMessageRoute(baseType, addressB); - - Assert.AreEqual(1, router.GetDestinationFor(baseType).Count); - } - - public class Message1 - { - - } - - public class Message2 - { - - } - - public class BaseMessage : IEvent - { - - } - - public class InheritedMessage : BaseMessage - { - - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/TypeRouteSourceTests.cs b/src/NServiceBus.Core.Tests/Routing/TypeRouteSourceTests.cs new file mode 100644 index 00000000000..4e98e09cebf --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/TypeRouteSourceTests.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Linq; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class TypeRouteSourceTests + { + [Test] + public void It_throws_if_specified_type_is_not_a_message() + { + var source = new TypeRouteSource(typeof(NonMessage), UnicastRoute.CreateFromEndpointName("Destination")); + Assert.That(() => source.GenerateRoutes(new Conventions()).ToArray(), Throws.Exception.Message.Contains("it is not considered a message")); + } + + class NonMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/UnicastPublisherRouterTests.cs b/src/NServiceBus.Core.Tests/Routing/UnicastPublisherRouterTests.cs new file mode 100644 index 00000000000..9e5ba88720f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/UnicastPublisherRouterTests.cs @@ -0,0 +1,135 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Routing; + using NUnit.Framework; + using Unicast.Messages; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + [TestFixture] + public class UnicastPublisherRouterTests + { + UnicastPublishRouter router; + MessageMetadataRegistry metadataRegistry; + EndpointInstances endpointInstances; + FakeSubscriptionStorage subscriptionStorage; + + [Test] + public async Task When_subscriber_does_not_define_logical_endpoint_should_send_event_to_each_address() + { + subscriptionStorage.Subscribers.Add(new Subscriber("address1", null)); + subscriptionStorage.Subscribers.Add(new Subscriber("address2", null)); + + var routes = await router.Route(typeof(Event), new DistributionPolicy(), new ContextBag()); + + var destinations = routes.Select(ExtractDestination).ToList(); + Assert.AreEqual(2, destinations.Count); + Assert.Contains("address1", destinations); + Assert.Contains("address2", destinations); + } + + [Test] + public async Task When_multiple_subscribers_for_logical_endpoints_should_route_event_to_a_single_instance_of_each_logical_endpoint() + { + var sales = "Sales"; + var shipping = "Shipping"; + subscriptionStorage.Subscribers.Add(new Subscriber("sales1", sales)); + subscriptionStorage.Subscribers.Add(new Subscriber("sales2", sales)); + subscriptionStorage.Subscribers.Add(new Subscriber("shipping1", shipping)); + subscriptionStorage.Subscribers.Add(new Subscriber("shipping2", shipping)); + + var routes = (await router.Route(typeof(Event), new DistributionPolicy(), new ContextBag())).ToArray(); + + var destinations = routes.Select(ExtractDestination).ToList(); + Assert.AreEqual(2, destinations.Count); + Assert.Contains("sales1", destinations); + Assert.Contains("shipping1", destinations); + } + + [Test] + public async Task Should_not_route_multiple_copies_of_message_to_one_physical_destination() + { + subscriptionStorage.Subscribers.Add(new Subscriber("address", null)); + subscriptionStorage.Subscribers.Add(new Subscriber("address", null)); + subscriptionStorage.Subscribers.Add(new Subscriber("address", "sales")); + subscriptionStorage.Subscribers.Add(new Subscriber("address", "sales")); + subscriptionStorage.Subscribers.Add(new Subscriber("address", "shipping")); + + var routes = await router.Route(typeof(Event), new DistributionPolicy(), new ContextBag()); + + Assert.AreEqual(1, routes.Count()); + Assert.AreEqual("address", ExtractDestination(routes.Single())); + } + + [Test] + public async Task Should_not_route_events_to_configured_endpoint_instances() + { + var logicalEndpoint = "sales"; + subscriptionStorage.Subscribers.Add(new Subscriber("address", logicalEndpoint)); + endpointInstances.AddOrReplaceInstances("A", new List + { + new EndpointInstance(logicalEndpoint, "1"), + new EndpointInstance(logicalEndpoint, "2") + }); + + var routes = await router.Route(typeof(Event), new DistributionPolicy(), new ContextBag()); + + Assert.AreEqual(1, routes.Count()); + Assert.AreEqual("address", ExtractDestination(routes.First())); + } + + [Test] + public async Task Should_return_empty_list_when_no_routes_found() + { + var routes = await router.Route(typeof(Event), new DistributionPolicy(), new ContextBag()); + + Assert.IsEmpty(routes); + } + + static string ExtractDestination(UnicastRoutingStrategy route) + { + var headers = new Dictionary(); + var addressTag = (UnicastAddressTag)route.Apply(headers); + return addressTag.Destination; + } + + [SetUp] + public void Setup() + { + metadataRegistry = new MessageMetadataRegistry(new Conventions()); + endpointInstances = new EndpointInstances(); + subscriptionStorage = new FakeSubscriptionStorage(); + router = new UnicastPublishRouter( + metadataRegistry, + subscriptionStorage); + } + + class FakeSubscriptionStorage : ISubscriptionStorage + { + public List Subscribers { get; }= new List(); + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + throw new NotImplementedException(); + } + + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) + { + return Task.FromResult>(Subscribers); + } + } + + class Event : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/UnicastRoutingTableTests.cs b/src/NServiceBus.Core.Tests/Routing/UnicastRoutingTableTests.cs new file mode 100644 index 00000000000..5aa6b2c404d --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/UnicastRoutingTableTests.cs @@ -0,0 +1,77 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + class UnicastRoutingTableTests + { + [Test] + public void When_group_does_not_exist_should_add_routes() + { + var routingTable = new UnicastRoutingTable(); + var route = UnicastRoute.CreateFromEndpointName("Endpoint1"); + routingTable.AddOrReplaceRoutes("key", new List() + { + new RouteTableEntry(typeof(Command), route), + }); + + var retrievedRoute = routingTable.GetRouteFor(typeof(Command)); + Assert.AreSame(route, retrievedRoute); + } + + [Test] + public void When_group_exists_should_replace_existing_routes() + { + var routingTable = new UnicastRoutingTable(); + var oldRoute = UnicastRoute.CreateFromEndpointName("Endpoint1"); + var newRoute = UnicastRoute.CreateFromEndpointName("Endpoint2"); + routingTable.AddOrReplaceRoutes("key", new List() + { + new RouteTableEntry(typeof(Command), oldRoute), + }); + + routingTable.AddOrReplaceRoutes("key", new List() + { + new RouteTableEntry(typeof(Command), newRoute), + }); + + var retrievedRoute = routingTable.GetRouteFor(typeof(Command)); + Assert.AreSame(newRoute, retrievedRoute); + } + + [Test] + public void When_routes_are_ambiguous_should_throw_exception() + { + var routingTable = new UnicastRoutingTable(); + var lowPriorityRoute = UnicastRoute.CreateFromEndpointName("Endpoint1"); + var highPriorityRoute = UnicastRoute.CreateFromEndpointName("Endpoint2"); + + routingTable.AddOrReplaceRoutes("key2", new List() + { + new RouteTableEntry(typeof(Command), highPriorityRoute), + }); + + Assert.That(() => + { + routingTable.AddOrReplaceRoutes("key1", new List() + { + new RouteTableEntry(typeof(Command), lowPriorityRoute), + }); + }, Throws.Exception); + } + + class Command + { + } + + class Command2 + { + } + + class Command3 + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Routing/UnicastSendRouterTests.cs b/src/NServiceBus.Core.Tests/Routing/UnicastSendRouterTests.cs new file mode 100644 index 00000000000..2e7066103dd --- /dev/null +++ b/src/NServiceBus.Core.Tests/Routing/UnicastSendRouterTests.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Collections.Generic; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class UnicastSendRouterTests + { + UnicastSendRouter router; + UnicastRoutingTable routingTable; + EndpointInstances endpointInstances; + + [Test] + public void When_routing_command_to_logical_endpoint_without_configured_instances_should_route_to_a_single_destination() + { + var logicalEndpointName = "Sales"; + routingTable.AddOrReplaceRoutes("A", new List {new RouteTableEntry(typeof(Command), UnicastRoute.CreateFromEndpointName(logicalEndpointName)) }); + + var route = router.Route(typeof(Command), new DistributionPolicy()); + + Assert.AreEqual(logicalEndpointName, ExtractDestination(route)); + } + + [Test] + public void When_multiple_dynamic_instances_for_logical_endpoints_should_route_message_to_a_single_instance() + { + var sales = "Sales"; + routingTable.AddOrReplaceRoutes("A", new List { new RouteTableEntry(typeof(Command), UnicastRoute.CreateFromEndpointName(sales)) }); + + endpointInstances.AddOrReplaceInstances("A", new List + { + new EndpointInstance(sales, "1"), + new EndpointInstance(sales, "2"), + }); + + var route = router.Route(typeof(Command), new DistributionPolicy()); + + Assert.AreEqual("Sales-1", ExtractDestination(route)); + } + + [Test] + public void Should_return_empty_list_when_no_routes_found() + { + var route = router.Route(typeof(Command), new DistributionPolicy()); + + Assert.IsNull(route); + } + + static string ExtractDestination(UnicastRoutingStrategy route) + { + var headers = new Dictionary(); + var addressTag = (UnicastAddressTag) route.Apply(headers); + return addressTag.Destination; + } + + [SetUp] + public void Setup() + { + routingTable = new UnicastRoutingTable(); + endpointInstances = new EndpointInstances(); + router = new UnicastSendRouter( + routingTable, + endpointInstances, + i => i.ToString()); + } + + class Command : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/CustomFinderAdapterTests.cs b/src/NServiceBus.Core.Tests/Sagas/CustomFinderAdapterTests.cs new file mode 100644 index 00000000000..cc17dddf281 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/CustomFinderAdapterTests.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.Core.Tests.Sagas +{ + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Persistence; + using NServiceBus.Sagas; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using Testing; + + [TestFixture] + public class CustomFinderAdapterTests + { + [Test] + public void Throws_friendly_exception_when_IFindSagas_FindBy_returns_null() + { + var availableTypes = new List + { + typeof(ReturnsNullFinder) + }; + + var messageType = typeof(StartSagaMessage); + + var messageConventions = new Conventions + { + IsCommandTypeAction = t => t == messageType + }; + + var sagaMetadata = SagaMetadata.Create(typeof(TestSaga), availableTypes, messageConventions); + + SagaFinderDefinition finderDefinition; + + if (!sagaMetadata.TryGetFinder(messageType.FullName, out finderDefinition)) + { + throw new Exception("Finder not found"); + } + + var builder = new FakeBuilder(); + + builder.Register(() => new ReturnsNullFinder()); + + var customerFinderAdapter = new CustomFinderAdapter(); + + Assert.That(async () => await customerFinderAdapter.Find(builder, finderDefinition, new InMemorySynchronizedStorageSession(), new ContextBag(), new StartSagaMessage()), + Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + } + + class TestSaga : Saga, IAmStartedByMessages + { + internal class SagaData : ContainSagaData + { + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + } + + class StartSagaMessage + { } + + class ReturnsNullFinder : IFindSagas.Using + { + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return null; + } + } +} diff --git a/src/NServiceBus.Core.Tests/Sagas/InvokeSagaNotFoundBehaviorTests.cs b/src/NServiceBus.Core.Tests/Sagas/InvokeSagaNotFoundBehaviorTests.cs new file mode 100644 index 00000000000..2b5873c2adb --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/InvokeSagaNotFoundBehaviorTests.cs @@ -0,0 +1,73 @@ +namespace NServiceBus.Core.Tests.Sagas +{ + using NServiceBus.Pipeline; + using NServiceBus.Sagas; + using NUnit.Framework; + using System.Threading.Tasks; + using Testing; + + [TestFixture] + public class InvokeSagaNotFoundBehaviorTests + { + [SetUp] + public void SetupTests() + { + behavior = new InvokeSagaNotFoundBehavior(); + + incomingContext = new TestableIncomingLogicalMessageContext(); + } + + [Test] + public void SagaNotFound_handlers_are_not_called_when_saga_is_found() + { + var validSagaHandler = new HandleSagaNotFoundValid(); + + incomingContext.Builder.Register(new HandleSagaNotFoundReturnsNull1(), validSagaHandler); + + Assert.That(async () => await behavior.Invoke(incomingContext, ctx => TaskEx.CompletedTask), Throws.Nothing); + + Assert.False(validSagaHandler.Handled); + } + + [Test] + public void Throw_friendly_exception_when_any_IHandleSagaNotFound_Handler_returns_null() + { + incomingContext.Builder.Register(new HandleSagaNotFoundReturnsNull1(), new HandleSagaNotFoundValid()); + + Assert.That(async () => await behavior.Invoke(incomingContext, SetSagaNotFound), Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + + static Task SetSagaNotFound(IIncomingLogicalMessageContext context) + { + context.Extensions.Get().SagaNotFound(); + return TaskEx.CompletedTask; + } + + class TestMessage : IMessage + { } + + class HandleSagaNotFoundReturnsNull1 : IHandleSagaNotFound + { + public Task Handle(object message, IMessageProcessingContext context) + { + return null; + } + } + + class HandleSagaNotFoundValid : IHandleSagaNotFound + { + public bool Handled { get; private set; } + + public Task Handle(object message, IMessageProcessingContext context) + { + Handled = true; + + return TaskEx.CompletedTask; + } + } + + InvokeSagaNotFoundBehavior behavior; + TestableIncomingLogicalMessageContext incomingContext; + } +} diff --git a/src/NServiceBus.Core.Tests/Sagas/SagaMetadataCreationTests.cs b/src/NServiceBus.Core.Tests/Sagas/SagaMetadataCreationTests.cs new file mode 100644 index 00000000000..cfaf3116b91 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/SagaMetadataCreationTests.cs @@ -0,0 +1,729 @@ +namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using NServiceBus.Persistence; + using NServiceBus.Sagas; + using NUnit.Framework; + + [TestFixture] + public class SagaMetadataCreationTests + { + [Test] + public void Throws_when_does_not_implement_generic_saga() + { + Assert.Throws(() => SagaMetadata.Create(typeof(MyNonGenericSaga))); + } + + [Test] + public void GetEntityClrType() + { + var metadata = SagaMetadata.Create(typeof(MySaga)); + Assert.AreEqual(typeof(MySaga.MyEntity), metadata.SagaEntityType); + } + + [Test] + public void GetSagaClrType() + { + var metadata = SagaMetadata.Create(typeof(MySaga)); + + Assert.AreEqual(typeof(MySaga), metadata.SagaType); + } + + [Test] + public void DetectUniquePropertiesByAttribute() + { + var metadata = SagaMetadata.Create(typeof(MySaga)); + + SagaMetadata.CorrelationPropertyMetadata correlatedProperty; + + Assert.True(metadata.TryGetCorrelationProperty(out correlatedProperty)); + Assert.AreEqual("UniqueProperty", correlatedProperty.Name); + } + + [Test] + public void When_finder_for_non_message() + { + var availableTypes = new List + { + typeof(SagaWithNonMessageFinder.Finder) + }; + var exception = Assert.Throws(() => { SagaMetadata.Create(typeof(SagaWithNonMessageFinder), availableTypes, new Conventions()); }); + Assert.AreEqual("A custom IFindSagas must target a valid message type as defined by the message conventions. Change 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.SagaMetadataCreationTests+SagaWithNonMessageFinder+StartSagaMessage' to a valid message type or add it to the message conventions. Finder name 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.SagaMetadataCreationTests+SagaWithNonMessageFinder+Finder'.", exception.Message); + } + + [Test] + public void When_message_only_has_custom_finder() + { + var availableTypes = new List + { + typeof(SagaWithFinderOnly.Finder) + }; + var metadata = SagaMetadata.Create(typeof(SagaWithFinderOnly), availableTypes, new Conventions()); + Assert.AreEqual(1, metadata.Finders.Count); + Assert.AreEqual(typeof(CustomFinderAdapter), metadata.Finders.First().Type); + } + + [Test] + public void When_a_finder_and_a_mapping_exists_for_same_property() + { + var availableTypes = new List + { + typeof(SagaWithMappingAndFinder.Finder) + }; + var exception = Assert.Throws(() => { SagaMetadata.Create(typeof(SagaWithMappingAndFinder), availableTypes, new Conventions()); }); + Assert.AreEqual("A custom IFindSagas and an existing mapping where found for message 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.SagaMetadataCreationTests+SagaWithMappingAndFinder+StartSagaMessage'. Either remove the message mapping for remove the finder. Finder name 'NServiceBus.Core.Tests.Sagas.TypeBasedSagas.SagaMetadataCreationTests+SagaWithMappingAndFinder+Finder'.", exception.Message); + } + + [Test] + public void HandleBothUniqueAttributeAndMapping() + { + var metadata = SagaMetadata.Create(typeof(MySagaWithMappedAndUniqueProperty)); + + SagaMetadata.CorrelationPropertyMetadata correlatedProperty; + + Assert.True(metadata.TryGetCorrelationProperty(out correlatedProperty)); + Assert.AreEqual("UniqueProperty", correlatedProperty.Name); + } + + [Test] + public void AutomaticallyAddUniqueForMappedProperties() + { + var metadata = SagaMetadata.Create(typeof(MySagaWithMappedProperty)); + SagaMetadata.CorrelationPropertyMetadata correlatedProperty; + + Assert.True(metadata.TryGetCorrelationProperty(out correlatedProperty)); + Assert.AreEqual("UniqueProperty", correlatedProperty.Name); + } + + [Test, Ignore("Not sure we should enforce this yet")] + public void RequireFinderForMessagesStartingTheSaga() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(MySagaWithUnmappedStartProperty))); + Assert.True(ex.Message.Contains(typeof(MySagaWithUnmappedStartProperty.MessageThatStartsTheSaga).FullName)); + } + + [Test] + public void HandleNonExistingFinders() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(MySagaWithUnmappedStartProperty))); + + Assert.That(ex.Message.Contains("mapper.ConfigureMapping")); + } + + [Test] + public void DetectMessagesStartingTheSaga() + { + var metadata = SagaMetadata.Create(typeof(SagaWith2StartersAnd1Handler)); + + var messages = metadata.AssociatedMessages; + + Assert.AreEqual(4, messages.Count); + + Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage1).FullName)); + + Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage2).FullName)); + + Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.Message3).FullName)); + + Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.MyTimeout).FullName)); + } + + [Test] + public void DetectAndRegisterPropertyFinders() + { + var metadata = SagaMetadata.Create(typeof(MySagaWithMappedProperty)); + + var finder = GetFinder(metadata, typeof(SomeMessage).FullName); + + Assert.AreEqual(typeof(PropertySagaFinder), finder.Type); + Assert.NotNull(finder.Properties["property-accessor"]); + Assert.AreEqual("UniqueProperty", finder.Properties["saga-property-name"]); + } + + [Test] + public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageProps() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithIdMappedToNonGuidMessageProperty))); + Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); + } + + [Test] + public void ValidateThatMappingOnSagaIdFromStringToGuidForMessagePropsThrowsException() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithIdMappedToStringMessageProperty))); + Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); + } + + [Test] + public void ValidateThatMappingOnNonSagaIdGuidPropertyFromStringToGuidForMessagePropsThrowsException() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithNonIdPropertyMappedToStringMessageProperty))); + Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); + } + + [Test] + public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageFields() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithIdMappedToNonGuidMessageField))); + Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); + } + + [Test] + public void ValidateThatSagaPropertyIsNotAField() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithSagaDataMemberAsFieldInsteadOfProperty))); + Assert.True(ex.Message.Contains(typeof(SagaWithSagaDataMemberAsFieldInsteadOfProperty.SagaData).Name)); + } + + [Test] + public void DetectAndRegisterCustomFindersUsingScanning() + { + var availableTypes = new List + { + typeof(MySagaWithScannedFinder.CustomFinder) + }; + var metadata = SagaMetadata.Create(typeof(MySagaWithScannedFinder), availableTypes, new Conventions()); + + var finder = GetFinder(metadata, typeof(SomeMessage).FullName); + + Assert.AreEqual(typeof(CustomFinderAdapter), finder.Type); + Assert.AreEqual(typeof(MySagaWithScannedFinder.CustomFinder), finder.Properties["custom-finder-clr-type"]); + } + + [Test] + public void ValidateThrowsWhenSagaMapsMessageItDoesntHandle() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaThatMapsMessageItDoesntHandle))); + + Assert.That(ex.Message.Contains("does not handle that message") && ex.Message.Contains("in the ConfigureHowToFindSaga method")); + } + + [Test] + public void ValidateThrowsWhenSagaCustomFinderMapsMessageItDoesntHandle() + { + var availableTypes = new List + { + typeof(SagaWithCustomFinderForMessageItDoesntHandle.Finder) + }; + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithCustomFinderForMessageItDoesntHandle), availableTypes, new Conventions())); + + Assert.That(ex.Message.Contains("does not handle that message") && ex.Message.Contains("Custom saga finder")); + } + + [Test] + public void GetEntityClrTypeFromInheritanceChain() + { + var metadata = SagaMetadata.Create(typeof(SagaWithInheritanceChain)); + + Assert.AreEqual(typeof(SagaWithInheritanceChain.SagaData), metadata.SagaEntityType); + } + + SagaFinderDefinition GetFinder(SagaMetadata metadata, string messageType) + { + SagaFinderDefinition finder; + + if (!metadata.TryGetFinder(messageType, out finder)) + { + throw new Exception("Finder not found"); + } + + return finder; + } + + class MyNonGenericSaga : Saga + { + protected internal override void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) + { + } + } + + class MySaga : Saga, IAmStartedByMessages + { + public Task Handle(M1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.UniqueProperty).ToSaga(s => s.UniqueProperty); + } + + internal class MyEntity : ContainSagaData + { + public int UniqueProperty { get; set; } + } + } + + + class M1 + { + public int UniqueProperty { get; set; } + } + + public class SagaWithNonMessageFinder : Saga, + IAmStartedByMessages + { + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + // Does not need a mapping for StartSagaMessage because there is a SagaFinder + } + + public class SagaData : ContainSagaData + { + public string Property { get; set; } + } + + public class Finder : IFindSagas.Using + { + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return Task.FromResult(default(SagaData)); + } + } + + public class StartSagaMessage + { + public string Property { get; set; } + } + } + + public class SagaWithFinderOnly : Saga, + IAmStartedByMessages + { + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + // Does not need a mapping for StartSagaMessage because there is a SagaFinder + } + + public class SagaData : ContainSagaData + { + public string Property { get; set; } + } + + public class Finder : IFindSagas.Using + { + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return Task.FromResult(default(SagaData)); + } + } + + public class StartSagaMessage : IMessage + { + public string Property { get; set; } + } + } + + public class SagaWithMappingAndFinder : Saga, + IAmStartedByMessages + { + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.Property) + .ToSaga(s => s.Property); + } + + public class SagaData : ContainSagaData + { + public string Property { get; set; } + } + + public class Finder : IFindSagas.Using + { + public Task FindBy(StartSagaMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return Task.FromResult(default(SagaData)); + } + } + + public class StartSagaMessage : IMessage + { + public string Property { get; set; } + } + } + + class MySagaWithMappedAndUniqueProperty : Saga, IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeProperty) + .ToSaga(s => s.UniqueProperty); + } + + public class SagaData : ContainSagaData + { + public int UniqueProperty { get; set; } + } + } + + class MySagaWithMappedProperty : Saga, IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeProperty) + .ToSaga(s => s.UniqueProperty); + } + + public class SagaData : ContainSagaData + { + public int UniqueProperty { get; set; } + } + } + + class StartMessage + { + } + + class MySagaWithUnmappedStartProperty : Saga, + IAmStartedByMessages, + IHandleMessages + { + public Task Handle(MessageThatStartsTheSaga message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + public Task Handle(MessageThatDoesNotStartTheSaga message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + // Saga does not contain mappings on purpose, and should throw an exception + } + + public class MessageThatStartsTheSaga : IMessage + { + public int SomeProperty { get; set; } + } + + public class MessageThatDoesNotStartTheSaga : IMessage + { + public int SomeProperty { get; set; } + } + + public class SagaData : ContainSagaData + { + } + } + + + class SagaWith2StartersAnd1Handler : Saga, + IAmStartedByMessages, + IAmStartedByMessages, + IHandleMessages, + IHandleTimeouts + { + public Task Handle(StartMessage1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public Task Handle(StartMessage2 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public Task Handle(Message3 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public Task Timeout(MyTimeout state, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + mapper.ConfigureMapping(m => m.SomeId) + .ToSaga(s => s.SomeId); + } + + public class StartMessage1 : IMessage + { + public string SomeId { get; set; } + } + + public class StartMessage2 : IMessage + { + public string SomeId { get; set; } + } + + public class Message3 : IMessage + { + } + + public class SagaData : ContainSagaData + { + public string SomeId { get; set; } + } + + public class MyTimeout + { + } + } + + class SagaWithIdMappedToStringMessageProperty : Saga, + IAmStartedByMessages + { + public class SagaData : ContainSagaData + { + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.StringProperty) + .ToSaga(s => s.Id); + } + + public Task Handle(SomeMessageWithStringProperty message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + } + + class SagaWithNonIdPropertyMappedToStringMessageProperty : Saga, + IAmStartedByMessages + { + public class SagaData : ContainSagaData + { + public Guid NonIdColumn { get; set; } + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.StringProperty) + .ToSaga(s => s.NonIdColumn); + } + + public Task Handle(SomeMessageWithStringProperty message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + } + + class SagaWithIdMappedToNonGuidMessageProperty : Saga, + IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeProperty) + .ToSaga(s => s.Id); + } + + public class SagaData : ContainSagaData + { + } + } + + class SagaWithIdMappedToNonGuidMessageField : Saga, + IAmStartedByMessages + { + public Task Handle(SomeMessageWithField message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeProperty) + .ToSaga(s => s.Id); + } + + public class SagaData : ContainSagaData + { + } + } + + class SagaWithSagaDataMemberAsFieldInsteadOfProperty : Saga, + IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.SomeProperty) + .ToSaga(s => s.SomeField); + } + + public class SagaData : ContainSagaData + { + public int SomeField = 0; + } + } + + class MySagaWithScannedFinder : Saga, IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + // Does not need a mapping for SomeMessage because a saga finder exists + } + + public class SagaData : ContainSagaData + { + } + + internal class CustomFinder : IFindSagas.Using + { + public Task FindBy(SomeMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return Task.FromResult(default(SagaData)); + } + } + } + + class SagaWithInheritanceChain : SagaWithInheritanceChainBase, IAmStartedByMessages + { + public Task Handle(SomeMessageWithStringProperty message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public class SagaData : ContainSagaData + { + public string SomeId { get; set; } + } + + public class SomeOtherData + { + public string SomeData { get; set; } + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + base.ConfigureHowToFindSaga(mapper); + mapper.ConfigureMapping(message => message.StringProperty).ToSaga(saga => saga.SomeId); + } + } + + class SagaThatMapsMessageItDoesntHandle : Saga, + IAmStartedByMessages + { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeProperty).ToSaga(saga => saga.SomeProperty); + mapper.ConfigureMapping(msg => msg.SomeProperty).ToSaga(saga => saga.SomeProperty); + } + + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public class SagaData : ContainSagaData + { + public int SomeProperty { get; set; } + } + + public class OtherMessage : IMessage + { + public int SomeProperty { get; set; } + } + } + + class SagaWithCustomFinderForMessageItDoesntHandle : Saga, + IAmStartedByMessages + { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(msg => msg.SomeProperty).ToSaga(saga => saga.SomeProperty); + } + + public Task Handle(SomeMessage message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + public class Finder : IFindSagas.Using + { + public Task FindBy(OtherMessage message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context) + { + return Task.FromResult(default(SagaData)); + } + } + + public class SagaData : ContainSagaData + { + public int SomeProperty { get; set; } + } + + public class OtherMessage : IMessage + { + public int SomeProperty { get; set; } + } + } + + class SagaWithInheritanceChainBase : Saga + where T : IContainSagaData, new() + where O : class + { + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + } + } + + class SomeMessageWithField : IMessage + { +#pragma warning disable 649 + public int SomeProperty; +#pragma warning restore 649 + } + + class SomeMessage : IMessage + { + public int SomeProperty { get; set; } + } + + class SomeMessageWithStringProperty : IMessage + { + public string StringProperty { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/SagaModelTests.cs b/src/NServiceBus.Core.Tests/Sagas/SagaModelTests.cs index 78efef15368..80f6e04a3bd 100644 --- a/src/NServiceBus.Core.Tests/Sagas/SagaModelTests.cs +++ b/src/NServiceBus.Core.Tests/Sagas/SagaModelTests.cs @@ -2,18 +2,18 @@ namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas { using System; using System.Linq; - using NServiceBus.Saga; + using System.Threading.Tasks; using NServiceBus.Sagas; using NUnit.Framework; - using Conventions = NServiceBus.Conventions; [TestFixture] public class SagaModelTests { - - SagaMetaModel GetModel(params Type[] types) + SagaMetadataCollection GetModel(params Type[] types) { - return new SagaMetaModel(TypeBasedSagaMetaModel.Create(types.ToList(),new Conventions())); + var sagaMetaModel = new SagaMetadataCollection(); + sagaMetaModel.Initialize(types.ToList(), new Conventions()); + return sagaMetaModel; } [Test] @@ -21,7 +21,7 @@ public void FindSagasByName() { var model = GetModel(typeof(MySaga)); - var metadata = model.FindByName(typeof(MySaga).FullName); + var metadata = model.Find(typeof(MySaga)); Assert.NotNull(metadata); @@ -32,49 +32,89 @@ public void FindSagasByEntityName() { var model = GetModel(typeof(MySaga)); - var metadata = model.FindByEntityName(typeof(MySaga.MyEntity).FullName); + var metadata = model.FindByEntity(typeof(MySaga.MyEntity)); Assert.NotNull(metadata); } - + [Test] + public void ValidateAssumptionsAboutSagaMappings() + { + var model = GetModel(typeof(MySaga)); + + var metadata = model.Find(typeof(MySaga)); + + Assert.NotNull(metadata); + + Assert.AreEqual(typeof(MySaga.MyEntity), metadata.SagaEntityType); + Assert.AreEqual(typeof(MySaga.MyEntity).FullName, metadata.EntityName); + Assert.AreEqual(typeof(MySaga), metadata.SagaType); + Assert.AreEqual(typeof(MySaga).FullName, metadata.Name); + + Assert.AreEqual(2, metadata.AssociatedMessages.Count); + Assert.AreEqual(1, metadata.AssociatedMessages.Count(am => am.MessageTypeName == typeof(Message1).FullName && am.IsAllowedToStartSaga)); + Assert.AreEqual(1, metadata.AssociatedMessages.Count(am => am.MessageTypeName == typeof(Message2).FullName && !am.IsAllowedToStartSaga)); + + SagaMetadata.CorrelationPropertyMetadata correlatedProperty; + + Assert.True(metadata.TryGetCorrelationProperty(out correlatedProperty)); + Assert.AreEqual("UniqueProperty", correlatedProperty.Name); + + Assert.AreEqual(2, metadata.Finders.Count); + Assert.AreEqual(1, metadata.Finders.Count(f => f.MessageType == typeof(Message1))); + Assert.AreEqual(1, metadata.Finders.Count(f => f.MessageType == typeof(Message2))); + } [Test] public void FilterOutNonSagaTypes() { - var model = GetModel(typeof(MySaga),typeof(string)); + var model = GetModel(typeof(MySaga), typeof(string), typeof(AbstractSaga)).ToList(); - Assert.AreEqual(1, model.Count()); + Assert.That(model, Has.Exactly(1).Matches(x => x.SagaType == typeof(MySaga))); } - class MySaga : Saga,IHandleMessages + class MySaga : Saga, IAmStartedByMessages, IHandleMessages { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + public Task Handle(Message1 message, IMessageHandlerContext context) { - + throw new NotImplementedException(); } - public class MyEntity : ContainSagaData + public Task Handle(Message2 message, IMessageHandlerContext context) { - [Unique] - public int UniqueProperty { get; set; } + throw new NotImplementedException(); } - public void Handle(Message1 message) + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) { - throw new NotImplementedException(); + mapper.ConfigureMapping(m => m.UniqueProperty).ToSaga(s => s.UniqueProperty); + mapper.ConfigureMapping(m => m.UniqueProperty).ToSaga(s => s.UniqueProperty); } - public void Handle(Message2 message) + public class MyEntity : ContainSagaData { - throw new NotImplementedException(); + public int UniqueProperty { get; set; } } } - class Message1 : IMessage { } - class Message2 : IMessage { } + abstract class AbstractSaga : Saga, IAmStartedByMessages + { + public abstract Task Handle(Message1 message, IMessageHandlerContext context); + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + } + class Message1 : IMessage + { + public int UniqueProperty { get; set; } + } + class Message2 : IMessage + { + public int UniqueProperty { get; set; } + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/TypeBasedSagaMetaModelTests.cs b/src/NServiceBus.Core.Tests/Sagas/TypeBasedSagaMetaModelTests.cs deleted file mode 100644 index 9ef3f0d7cac..00000000000 --- a/src/NServiceBus.Core.Tests/Sagas/TypeBasedSagaMetaModelTests.cs +++ /dev/null @@ -1,419 +0,0 @@ -namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NServiceBus.Saga; - using NServiceBus.Sagas; - using NUnit.Framework; - using Conventions = NServiceBus.Conventions; - - [TestFixture] - public class TypeBasedSagaMetaModelTests - { - [Test] - public void Throws_when_does_not_implement_generic_saga() - { - Assert.Throws(() => TypeBasedSagaMetaModel.Create(typeof(MyNonGenericSaga))); - } - - class MyNonGenericSaga : Saga - { - protected internal override void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) - { - } - } - - [Test] - public void GetEntityClrType() - { - - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga)); - - - Assert.AreEqual(typeof(MyEntity), metadata.SagaEntityType); - } - - [Test] - public void GetSagaClrType() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga)); - - Assert.AreEqual(typeof(MySaga), metadata.SagaType); - } - - [Test] - public void DetectUniquePropertiesByAttribute() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySaga)); - - - Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name); - } - - [Test] - public void HandleBothUniqueAttributeAndMapping() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedAndUniqueProperty)); - - - Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name); - } - - [Test] - public void AutomaticallyAddUniqueForMappedProperties() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedProperty)); - - - Assert.AreEqual("UniqueProperty", metadata.CorrelationProperties.Single().Name); - } - - [Test, Ignore("Not sure we should enforce this yet")] - public void RequireFinderForMessagesStartingTheSaga() - { - var ex = Assert.Throws(() => TypeBasedSagaMetaModel.Create(typeof(MySagaWithUnmappedStartProperty))); - - - Assert.True(ex.Message.Contains(typeof(MessageThatStartsTheSaga).FullName)); - } - - [Test] - public void HandleNonExistingFinders() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithUnmappedStartProperty)); - SagaFinderDefinition finder; - - Assert.False(metadata.TryGetFinder(typeof(MessageThatStartsTheSaga).FullName, out finder)); - } - - [Test] - public void DetectMessagesStartingTheSaga() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(SagaWith2StartersAnd1Handler)); - - var messages = metadata.AssociatedMessages; - - Assert.AreEqual(4, messages.Count()); - - Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage1).FullName)); - - Assert.True(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.StartMessage2).FullName)); - - Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.Message3).FullName)); - - Assert.False(metadata.IsMessageAllowedToStartTheSaga(typeof(SagaWith2StartersAnd1Handler.MyTimeout).FullName)); - } - - - [Test] - public void DetectAndRegisterPropertyFinders() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithMappedProperty)); - - var finder = GetFinder(metadata, typeof(SomeMessage).FullName); - - Assert.AreEqual(typeof(PropertySagaFinder), finder.Type); - Assert.NotNull(finder.Properties["property-accessor"]); - Assert.AreEqual("UniqueProperty", finder.Properties["saga-property-name"]); - } - - [Test] - public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageProps() - { - var ex = Assert.Throws(()=>TypeBasedSagaMetaModel.Create(typeof(SagaWithIdMappedToNonGuidMessageProperty))); - - - Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); - } - - [Test] - public void ValidateThatMappingOnSagaIdHasTypeGuidForMessageFields() - { - var ex = Assert.Throws(() => TypeBasedSagaMetaModel.Create(typeof(SagaWithIdMappedToNonGuidMessageField))); - - - Assert.True(ex.Message.Contains(typeof(SomeMessage).Name)); - } - - [Test] - public void DetectAndRegisterCustomFindersUsingScanning() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(MySagaWithScannedFinder), - new List - { - typeof(MySagaWithScannedFinder.CustomFinder) - }, new Conventions()); - - var finder = GetFinder(metadata, typeof(SomeMessage).FullName); - - Assert.AreEqual(typeof(CustomFinderAdapter), finder.Type); - Assert.AreEqual(typeof(MySagaWithScannedFinder.CustomFinder), finder.Properties["custom-finder-clr-type"]); - } - - [Test] - public void GetEntityClrTypeFromInheritanceChain() - { - var metadata = TypeBasedSagaMetaModel.Create(typeof(SagaWithInheritanceChain)); - - Assert.AreEqual(typeof(SagaWithInheritanceChain.SagaData), metadata.SagaEntityType); - } - - SagaFinderDefinition GetFinder(SagaMetadata metadata, string messageType) - { - SagaFinderDefinition finder; - - if (!metadata.TryGetFinder(messageType, out finder)) - { - throw new Exception("Finder not found"); - } - - return finder; - } - - class MySagaWithMappedProperty : Saga - { - public class SagaData : ContainSagaData - { - public int UniqueProperty { get; set; } - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.SomeProperty) - .ToSaga(s => s.UniqueProperty); - } - } - - class MySagaWithMappedAndUniqueProperty : Saga - { - public class SagaData : ContainSagaData - { - [Unique] - public int UniqueProperty { get; set; } - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.SomeProperty) - .ToSaga(s => s.UniqueProperty); - } - } - - class MySagaWithScannedFinder : Saga, IAmStartedByMessages - { - public class SagaData : ContainSagaData - { - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - - internal class CustomFinder : IFindSagas.Using - { - public SagaData FindBy(SomeMessage message) - { - return null; - } - } - - public void Handle(SomeMessage message) - { - - } - } - - - class SagaWith2StartersAnd1Handler : Saga, - IAmStartedByMessages, - IAmStartedByMessages, - IHandleMessages, - IHandleTimeouts - { - - public class StartMessage1 : IMessage - { - public string SomeId { get; set; } - } - - public class StartMessage2 : IMessage - { - public string SomeId { get; set; } - } - - public class Message3 : IMessage - { - } - - public class SagaData : ContainSagaData - { - public string SomeId { get; set; } - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s => s.SomeId); - mapper.ConfigureMapping(m => m.SomeId) - .ToSaga(s => s.SomeId); - } - - public void Handle(StartMessage1 message) - { - throw new NotImplementedException(); - } - - public void Handle(StartMessage2 message) - { - throw new NotImplementedException(); - } - - public void Handle(Message3 message) - { - throw new NotImplementedException(); - } - - public class MyTimeout - { - } - - public void Timeout(MyTimeout state) - { - } - } - - - class MySaga : Saga - { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - - } - } - - class MyEntity : ContainSagaData - { - [Unique] - public int UniqueProperty { get; set; } - } - - class MySagaWithUnmappedStartProperty : Saga, - IAmStartedByMessages, - IHandleMessages - { - - - public class SagaData : ContainSagaData - { - - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - - - } - - public void Handle(MessageThatStartsTheSaga message) - { - - - } - - public void Handle(MessageThatDoesntStartTheSaga message) - { - - } - } - - class SagaWithIdMappedToNonGuidMessageProperty : Saga, - IAmStartedByMessages - { - - - public class SagaData : ContainSagaData - { - - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m=>m.SomeProperty) - .ToSaga(s=>s.Id); - } - - - public void Handle(SomeMessage message) - { - - } - } - - class SagaWithIdMappedToNonGuidMessageField : Saga, - IAmStartedByMessages - { - - - public class SagaData : ContainSagaData - { - - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m=>m.SomeProperty) - .ToSaga(s=>s.Id); - } - - - public void Handle(SomeMessageWithField message) - { - - } - } - - class SagaWithInheritanceChain : SagaWithInheritanceChainBase - { - public class SagaData : ContainSagaData - { - public string SomeId { get; set; } - } - - public class SomeOtherData - { - public string SomeData { get; set; } - } - } - - class SagaWithInheritanceChainBase : Saga where T : IContainSagaData, new() where O : class - { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - } - - class SomeMessageWithField:IMessage - { -#pragma warning disable 649 - public int SomeProperty; -#pragma warning restore 649 - } - - class SomeMessage : IMessage - { - public int SomeProperty { get; set; } - } - - class MessageThatDoesntStartTheSaga : IMessage - { - public int SomeProperty { get; set; } - } - - - class MessageThatStartsTheSaga : IMessage - { - public int SomeProperty { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/UniqueAttributeTests.cs b/src/NServiceBus.Core.Tests/Sagas/UniqueAttributeTests.cs deleted file mode 100644 index 480709762aa..00000000000 --- a/src/NServiceBus.Core.Tests/Sagas/UniqueAttributeTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace NServiceBus.Core.Tests.Sagas -{ - using System; - using System.Linq; - using NUnit.Framework; - using Saga; - - [TestFixture] - public class UniqueAttributeTests - { - - [Test] - public void Ensure_inherited_property_is_returned_when_attribute_exists() - { - var uniqueProperties = UniqueAttribute.GetUniqueProperties(typeof(InheritedModel)) - .ToList(); - Assert.AreEqual(1,uniqueProperties.Count); - Assert.AreEqual("PropertyWithAttribute", uniqueProperties.First().Name); - } - - [Test] - public void EnsureOverridePropertyIsReturnedWhenAttributeExists() - { - var uniqueProperties = UniqueAttribute.GetUniqueProperties(typeof(InheritedModelWithOverride)) - .ToList(); - Assert.AreEqual(1,uniqueProperties.Count); - Assert.AreEqual("PropertyWithAttribute", uniqueProperties.First().Name); - } - - [Test] - public void Ensure_property_is_returned_when_attribute_exists() - { - var uniqueProperties = UniqueAttribute.GetUniqueProperties(typeof(ModelWithUniqueProperty)) - .ToList(); - Assert.AreEqual(1,uniqueProperties.Count); - Assert.AreEqual("PropertyWithAttribute", uniqueProperties.First().Name); - } - - [Test] - public void Ensure_multiple_properties_are_returned_when_multiple_attributes_exists() - { - var uniqueProperties = UniqueAttribute.GetUniqueProperties(typeof(ModelWithMultipleUniqueProperty)) - .ToList(); - Assert.AreEqual(2,uniqueProperties.Count); - Assert.AreEqual("PropertyWithAttribute1", uniqueProperties.First().Name); - Assert.AreEqual("PropertyWithAttribute2", uniqueProperties.Skip(1).First().Name); - } - - [Test] - public void Ensure_exception_is_thrown_when_multiple_attributes_exists() - { - Assert.Throws(() => UniqueAttribute.GetUniqueProperty(typeof(ModelWithMultipleUniqueProperty))); - } - - [Test] - public void Ensure_single_property_is_returned_when_attribute_exists() - { - var uniqueProperty = UniqueAttribute.GetUniqueProperty(typeof(ModelWithUniqueProperty)); - Assert.IsNotNull(uniqueProperty); - } - - [Test] - public void Ensure_property_is_returned_when_no_attribute_exists() - { - var uniqueProperties = UniqueAttribute.GetUniqueProperties(typeof(ModelWithNoUniqueProperty)); - Assert.IsEmpty(uniqueProperties); - } - - [Test] - public void Ensure_null_returned_when_no_attributes_exists() - { - var uniqueProperty = UniqueAttribute.GetUniqueProperty(typeof(ModelWithNoUniqueProperty)); - Assert.IsNull(uniqueProperty); - } - - public class ModelWithMultipleUniqueProperty - { - [Unique] - public string PropertyWithAttribute1 { get; set; } - [Unique] - public string PropertyWithAttribute2 { get; set; } - } - - public class ModelWithUniqueProperty - { - [Unique] - public virtual string PropertyWithAttribute { get; set; } - public string PropertyWithNoAttribute { get; set; } - } - public class InheritedModelWithOverride : ModelWithUniqueProperty - { - public override string PropertyWithAttribute { get; set; } - } - public class InheritedModel : ModelWithUniqueProperty - { - } - public class ModelWithNoUniqueProperty - { - public string PropertyWithAttribute { get; set; } - public string PropertyWithNoAttribute { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.Should_throw.approved.txt b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.Should_throw.approved.txt new file mode 100644 index 00000000000..1af1e428183 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.Should_throw.approved.txt @@ -0,0 +1 @@ +Sagas can only have mappings that correlate on a single saga property. Use custom finders to correlate NServiceBus.Core.Tests.Sagas.TypeBasedSagas.When_saga_has_multiple_correlated_properties+Message1,NServiceBus.Core.Tests.Sagas.TypeBasedSagas.When_saga_has_multiple_correlated_properties+Message2 to saga SagaWithMultipleCorrelatedProperties \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.cs b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.cs new file mode 100644 index 00000000000..e8fd13726fd --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_multiple_correlated_properties.cs @@ -0,0 +1,62 @@ +namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using ApprovalTests; + using NServiceBus.Sagas; + using NUnit.Framework; + + [TestFixture] + public class When_saga_has_multiple_correlated_properties + { + [Test] + public void Should_throw() + { + var exception = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithMultipleCorrelatedProperties), new List(), new Conventions())); + Approvals.Verify(exception.Message); + } + + class SagaWithMultipleCorrelatedProperties : Saga, + IAmStartedByMessages, + IAmStartedByMessages + { + public Task Handle(Message1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + public Task Handle(Message2 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m=>m.OrderId) + .ToSaga(s=>s.OrderId); + mapper.ConfigureMapping(m => m.LegacyOrderId) + .ToSaga(s => s.LegacyOrderId); + mapper.ConfigureMapping(m => m.Property2) + .ToSaga(s => s.OrderId); + } + + public class MyEntity : ContainSagaData + { + public string OrderId { get; set; } + public string LegacyOrderId { get; set; } + } + } + + class Message1 : IMessage + { + public string OrderId { get; set; } + public string Property2 { get; set; } + } + + class Message2 : IMessage + { + public string LegacyOrderId { get; set; } + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/When_saga_has_no_start_message.cs b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_no_start_message.cs new file mode 100644 index 00000000000..660a8bbb48f --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/When_saga_has_no_start_message.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NServiceBus.Sagas; + using NUnit.Framework; + + [TestFixture] + public class When_saga_has_no_start_message + { + [Test] + public void Should_throw() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithNoStartMessage), new List(), new Conventions())); + + StringAssert.Contains("Sagas must have at least one message that is allowed to start the saga", ex.Message); + } + + class SagaWithNoStartMessage : Saga, IHandleMessages + { + public Task Handle(Message1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + } + + public class MyEntity : ContainSagaData + { + } + } + + class Message1 : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Sagas/When_saga_is_correlated_on_a_unsupported_property_type.cs b/src/NServiceBus.Core.Tests/Sagas/When_saga_is_correlated_on_a_unsupported_property_type.cs new file mode 100644 index 00000000000..0ecf2a376cc --- /dev/null +++ b/src/NServiceBus.Core.Tests/Sagas/When_saga_is_correlated_on_a_unsupported_property_type.cs @@ -0,0 +1,44 @@ +namespace NServiceBus.Core.Tests.Sagas.TypeBasedSagas +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NServiceBus.Sagas; + using NUnit.Framework; + + [TestFixture] + public class When_saga_is_correlated_on_a_unsupported_property_type + { + [Test] + public void Should_throw() + { + var ex = Assert.Throws(() => SagaMetadata.Create(typeof(SagaWithNoStartMessage), new List(), new Conventions())); + + StringAssert.Contains("DateTime is not supported for correlated properties", ex.Message); + } + + class SagaWithNoStartMessage : Saga, IAmStartedByMessages + { + public Task Handle(Message1 message, IMessageHandlerContext context) + { + throw new NotImplementedException(); + } + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + { + mapper.ConfigureMapping(m => m.InvalidProp) + .ToSaga(s => s.InvalidProp); + } + + public class MyEntity : ContainSagaData + { + public DateTime InvalidProp { get; set; } + } + } + + class Message1 : IMessage + { + public DateTime InvalidProp { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherContext.cs b/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherContext.cs deleted file mode 100644 index 5272d2cc7db..00000000000 --- a/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherContext.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus.Core.Tests.Satellite -{ - using System; - using System.Reflection; - using System.Transactions; - using Fakes; - using NServiceBus.Faults; - using NUnit.Framework; - using Satellites; - using Settings; - using Unicast.Transport; - using TransactionSettings = Unicast.Transport.TransactionSettings; - - public abstract class SatelliteLauncherContext - { - protected FuncBuilder Builder; - protected IManageMessageFailures InMemoryFaultManager; - protected TransportReceiver Transport; - protected FakeReceiver FakeReceiver; - - [SetUp] - public void SetUp() - { - Builder = new FuncBuilder(); - InMemoryFaultManager = new NServiceBus.Faults.InMemory.FaultManager(); - FakeReceiver = new FakeReceiver(); - - var configurationBuilder = new BusConfiguration(); - - configurationBuilder.EndpointName("xyz"); - configurationBuilder.AssembliesToScan(new Assembly[0]); - - Transport = new TransportReceiver(new TransactionSettings(true, TimeSpan.FromSeconds(30), IsolationLevel.ReadCommitted, 5, false, false), 1, 0, FakeReceiver, InMemoryFaultManager, new SettingsHolder(), configurationBuilder.BuildConfiguration()); - - RegisterTypes(); - Builder.Register(() => InMemoryFaultManager); - Builder.Register(() => Transport); - - //var configurer = new SatelliteConfigurer(); - //configurer.Customize(configure); - - var launcher = new SatelliteLauncher(Builder); - - BeforeRun(); - launcher.Start(); - } - - public abstract void BeforeRun(); - public abstract void RegisterTypes(); - } -} diff --git a/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherTests.cs b/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherTests.cs deleted file mode 100644 index 36be673b103..00000000000 --- a/src/NServiceBus.Core.Tests/Satellite/SatelliteLauncherTests.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace NServiceBus.Core.Tests.Satellite -{ - using NUnit.Framework; - using Satellites; - - public class FakeSatellite : ISatellite - { - public bool Handle(TransportMessage message) - { - IsMessageHandled = true; - - return true; - } - - public Address InputAddress { get; set; } - public bool Disabled { get; set; } - - public virtual void Start() - { - IsStarted = true; - } - - public void Stop() - { - } - - public bool IsMessageHandled; - public bool IsStarted; - } - - public class SatelliteWithQueue : FakeSatellite - { - public SatelliteWithQueue() - { - InputAddress = new Address("input", "machineName"); - } - } - - [TestFixture] - public class TransportEventTests : SatelliteLauncherContext - { - readonly SatelliteWithQueue _sat = new SatelliteWithQueue(); - - public override void BeforeRun() - { - } - - public override void RegisterTypes() - { - Builder.Register(() => _sat); - } - - [Test] - public void When_a_message_is_received_the_Handle_method_should_called_on_the_satellite() - { - var tm = new TransportMessage(); - FakeReceiver.FakeMessageReceived(tm); - - Assert.That(_sat.IsMessageHandled, Is.True); - } - } - - [TestFixture] - public class TransportTests : SatelliteLauncherContext - { - readonly SatelliteWithQueue _satelliteWithQueue = new SatelliteWithQueue(); - - public override void BeforeRun() - { - } - - public override void RegisterTypes() - { - Builder.Register(() => _satelliteWithQueue); - } - - [Test] - public void The_transport_should_be_started() - { - Assert.That(FakeReceiver.IsStarted, Is.True); - } - - [Test] - public void The_transport_should_be_started_with_the_satellites_inputQueueAddress() - { - Assert.AreEqual(_satelliteWithQueue.InputAddress, FakeReceiver.InputAddress); - } - } - - [TestFixture] - public class DefaultsTests : SatelliteLauncherContext - { - readonly FakeSatellite _fakeSatellite = new FakeSatellite(); - - public override void BeforeRun() - { - } - - public override void RegisterTypes() - { - Builder.Register(() => _fakeSatellite); - } - - [Test] - public void By_default_the_satellite_should_not_be_disabled() - { - Assert.That(_fakeSatellite.Disabled, Is.False); - } - - [Test] - public void The_satellite_should_be_started() - { - Assert.That(_fakeSatellite.IsStarted, Is.True); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Scheduler/DefaultSchedulerTests.cs b/src/NServiceBus.Core.Tests/Scheduler/DefaultSchedulerTests.cs index 14029618fd6..a224b3578d5 100644 --- a/src/NServiceBus.Core.Tests/Scheduler/DefaultSchedulerTests.cs +++ b/src/NServiceBus.Core.Tests/Scheduler/DefaultSchedulerTests.cs @@ -1,66 +1,60 @@ namespace NServiceBus.Scheduling.Tests { - using System.Threading; - using Core.Tests.Fakes; + using System; + using System.Linq; + using System.Threading.Tasks; using NUnit.Framework; + using Testing; [TestFixture] public class DefaultSchedulerTests { - FakeBus bus = new FakeBus(); - DefaultScheduler scheduler; - [SetUp] public void SetUp() { - scheduler = new DefaultScheduler(bus); - } - - [Test] - public void When_scheduling_a_task_it_should_be_added_to_the_storage() - { - var task = new TaskDefinition(); - var taskId = task.Id; - scheduler.Schedule(task); - - Assert.IsTrue(scheduler.scheduledTasks.ContainsKey(taskId)); - } - - [Test] - public void When_scheduling_a_task_defer_should_be_called() - { - scheduler.Schedule(new TaskDefinition()); - Assert.That(bus.DeferWasCalled > 0); + scheduler = new DefaultScheduler(); } [Test] - public void When_starting_a_task_defer_should_be_called() + public async Task When_starting_a_task_defer_should_be_called() { - var task = new TaskDefinition {Task = () => { }}; + var task = new TaskDefinition + { + Every = TimeSpan.FromSeconds(5), + Task = c => TaskEx.CompletedTask + }; var taskId = task.Id; scheduler.Schedule(task); - var deferCount = bus.DeferWasCalled; - scheduler.Start(taskId); - - Assert.That(bus.DeferWasCalled > deferCount); + await scheduler.Start(taskId, handlingContext); + + Assert.That(handlingContext.SentMessages.Any(message => message.Options.GetDeliveryDelay().HasValue)); } [Test] - public void When_starting_a_task_the_lambda_should_be_executed() + public async Task When_starting_a_task_the_lambda_should_be_executed() { var i = 1; - var task = new TaskDefinition { Task = () => { i++; } }; + var task = new TaskDefinition + { + Every = TimeSpan.FromSeconds(5), + Task = c => + { + i++; + return TaskEx.CompletedTask; + } + }; var taskId = task.Id; - scheduler.Schedule(task); - scheduler.Start(taskId); - - Thread.Sleep(100); // Wait for the task... + scheduler.Schedule(task); + await scheduler.Start(taskId, handlingContext); Assert.That(i == 2); } + + TestableMessageHandlerContext handlingContext = new TestableMessageHandlerContext(); + DefaultScheduler scheduler; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Scheduler/ScheduleTests.cs b/src/NServiceBus.Core.Tests/Scheduler/ScheduleTests.cs index 4456d266c47..5ab0da52bfa 100644 --- a/src/NServiceBus.Core.Tests/Scheduler/ScheduleTests.cs +++ b/src/NServiceBus.Core.Tests/Scheduler/ScheduleTests.cs @@ -1,77 +1,94 @@ namespace NServiceBus.Scheduling.Tests { using System; - using System.Linq; using System.Threading.Tasks; - using Core.Tests; - using Core.Tests.Fakes; using NUnit.Framework; + using Testing; [TestFixture] public class ScheduleTests { - const string ACTION_NAME = "my action"; - FuncBuilder container = new FuncBuilder(); - FakeBus bus = new FakeBus(); - Schedule schedule; - - DefaultScheduler defaultScheduler; - [SetUp] public void SetUp() { - defaultScheduler = new DefaultScheduler(bus); - container.Register(() => bus); - container.Register(() => defaultScheduler); - - schedule = new Schedule(container); + scheduler = new DefaultScheduler(); + session = new FakeMessageSession(scheduler); } [Test] - public void When_scheduling_an_action_with_a_name_the_task_should_get_that_name() + public async Task When_scheduling_an_action_with_a_name_the_task_should_get_that_name() { - schedule.Every(TimeSpan.FromMinutes(5), ACTION_NAME, () => { }); - Assert.That(EnsureThatNameExists(ACTION_NAME)); - } + var wasCalled = true; + await session.ScheduleEvery(TimeSpan.FromMinutes(5), ACTION_NAME, c => + { + wasCalled = true; + return TaskEx.CompletedTask; + }); - [Test] - public void When_scheduling_an_action_without_a_name_the_task_should_get_the_DeclaringType_as_name() - { - schedule.Every(TimeSpan.FromMinutes(5), () => { }); - Assert.That(EnsureThatNameExists("ScheduleTests")); + Assert.IsTrue(wasCalled); + Assert.AreEqual(ACTION_NAME, session.ScheduledDefinition.Name); } [Test] - public void Ensure_retrieving_name_from_type_works_for_old_compiler() + public async Task When_scheduling_an_action_without_a_name_the_task_should_get_the_DeclaringType_as_name() { - schedule.Every(TimeSpan.FromMinutes(5), OldCompilerBits.ActionProvider.SimpleAction()); - Assert.That(EnsureThatNameExists("ActionProvider")); - } + var wasCalled = true; + await session.ScheduleEvery(TimeSpan.FromMinutes(5), c => + { + wasCalled = true; + return TaskEx.CompletedTask; + }); - [Test] - public void Ensure_retrieving_name_from_type_works_for_new_compiler() - { - schedule.Every(TimeSpan.FromMinutes(5), NewCompilerBits.ActionProvider.SimpleAction()); - Assert.That(EnsureThatNameExists("ActionProvider")); + Assert.IsTrue(wasCalled); + Assert.AreEqual(nameof(ScheduleTests), session.ScheduledDefinition.Name); } - [Test] - public void Schedule_tasks_using_multiple_threads() + DefaultScheduler scheduler; + FakeMessageSession session; + const string ACTION_NAME = "my action"; + + class FakeMessageSession : IMessageSession { - Parallel.For(0, 20, i => schedule.Every(TimeSpan.FromSeconds(1), () => { })); + public FakeMessageSession(DefaultScheduler defaultScheduler) + { + this.defaultScheduler = defaultScheduler; + } - bus.DeferWasCalled = 0; + public TaskDefinition ScheduledDefinition { get; private set; } - Parallel.ForEach(defaultScheduler.scheduledTasks, - t => new ScheduledTaskMessageHandler(defaultScheduler).Handle( - new Messages.ScheduledTask { TaskId = t.Key })); + public Task Send(object message, SendOptions options) + { + ScheduledDefinition = options.Context.Get().TaskDefinition; + defaultScheduler.Schedule(ScheduledDefinition); + return defaultScheduler.Start(ScheduledDefinition.Id, new TestablePipelineContext()); + } - Assert.That(bus.DeferWasCalled, Is.EqualTo(20)); - } + public Task Send(Action messageConstructor, SendOptions options) + { + throw new NotImplementedException(); + } - bool EnsureThatNameExists(string name) - { - return defaultScheduler.scheduledTasks.Any(task => task.Value.Name.Equals(name)); + public Task Publish(object message, PublishOptions options) + { + throw new NotImplementedException(); + } + + public Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + throw new NotImplementedException(); + } + + public Task Subscribe(Type eventType, SubscribeOptions options) + { + throw new NotImplementedException(); + } + + public Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + throw new NotImplementedException(); + } + + readonly DefaultScheduler defaultScheduler; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Scheduler/ScheduledTaskMessageHandlerTests.cs b/src/NServiceBus.Core.Tests/Scheduler/ScheduledTaskMessageHandlerTests.cs index 0728fab22f7..62f7d0f813a 100644 --- a/src/NServiceBus.Core.Tests/Scheduler/ScheduledTaskMessageHandlerTests.cs +++ b/src/NServiceBus.Core.Tests/Scheduler/ScheduledTaskMessageHandlerTests.cs @@ -1,13 +1,14 @@ namespace NServiceBus.Scheduling.Tests { using System; - using Core.Tests.Fakes; + using System.Linq; using NUnit.Framework; + using Testing; [TestFixture] public class ScheduledTaskMessageHandlerTests { - FakeBus bus = new FakeBus(); + TestableMessageHandlerContext handlingContext = new TestableMessageHandlerContext(); DefaultScheduler scheduler; ScheduledTaskMessageHandler handler; Guid taskId; @@ -15,10 +16,14 @@ public class ScheduledTaskMessageHandlerTests [SetUp] public void SetUp() { - scheduler = new DefaultScheduler(bus); + scheduler = new DefaultScheduler(); handler = new ScheduledTaskMessageHandler(scheduler); - var task = new TaskDefinition{Task = () => { }}; + var task = new TaskDefinition + { + Every = TimeSpan.FromSeconds(5), + Task = c => TaskEx.CompletedTask + }; taskId = task.Id; scheduler.Schedule(task); } @@ -26,8 +31,14 @@ public void SetUp() [Test] public void When_a_scheduledTask_message_is_handled_the_task_should_be_defer() { - handler.Handle(new Messages.ScheduledTask{TaskId = taskId}); - Assert.That(((Messages.ScheduledTask)bus.DeferMessages[0]).TaskId, Is.EqualTo(taskId)); + handler.Handle(new ScheduledTask + { + Every = TimeSpan.FromSeconds(5), + TaskId = taskId + }, handlingContext); + + var deferredMessage = handlingContext.SentMessages.First(message => message.Options.GetDeliveryDelay().HasValue).Message(); + Assert.That(deferredMessage.TaskId, Is.EqualTo(taskId)); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesProcessorTests.cs b/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesProcessorTests.cs deleted file mode 100644 index 770b24ba7e5..00000000000 --- a/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesProcessorTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace NServiceBus.Management.Retries.Tests -{ - using System; - using SecondLevelRetries; - using SecondLevelRetries.Helpers; - using NUnit.Framework; - - [TestFixture] - public class SecondLevelRetriesProcessorTests - { - readonly int[] _expectedResults = new[] - { - 10, - 20, - 30 - }; - - TransportMessage _message; - - [SetUp] - public void SetUp() - { - _message = new TransportMessage(); - } - - [Test] - public void The_time_span_should_increase_with_10_sec_for_every_retry() - { - var retriesProcessor = new SecondLevelRetriesProcessor - { - SecondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration() - }; - - for (var i = 0; i < 3; i++) - { - var timeSpan = retriesProcessor.SecondLevelRetriesConfiguration.RetryPolicy(_message); - - Defer(); - - Assert.AreEqual(_expectedResults[i], timeSpan.Seconds); - } - } - - [Test] - public void The_default_time_out_should_be_1_day() - { - TransportMessageHeaderHelper.SetHeader(_message, SecondLevelRetriesHeaders.RetriesTimestamp, DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow.AddDays(-1).AddSeconds(-1))); - - var retriesProcessor = new SecondLevelRetriesProcessor - { - SecondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration() - }; - - var hasTimedOut = retriesProcessor.SecondLevelRetriesConfiguration.RetryPolicy(_message) == TimeSpan.MinValue; - Assert.IsTrue(hasTimedOut); - } - - void Defer() - { - TransportMessageHeaderHelper.SetHeader(_message, Headers.Retries, (TransportMessageHeaderHelper.GetNumberOfRetries(_message) + 1).ToString()); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesTests.cs b/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesTests.cs deleted file mode 100644 index 5c63c57bc0f..00000000000 --- a/src/NServiceBus.Core.Tests/SecondLevelRetries/SecondLevelRetriesTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -namespace NServiceBus.Management.Retries.Tests -{ - using System; - using System.Collections.Generic; - using Faults.Forwarder; - using NServiceBus.Faults; - using SecondLevelRetries; - using SecondLevelRetries.Helpers; - using NUnit.Framework; - using Transports; - using Unicast; - - [TestFixture] - public class SecondLevelRetriesTests - { - SecondLevelRetriesProcessor satellite ; - FakeMessageSender messageSender ; - FakeMessageDeferrer deferrer ; - Address ERROR_QUEUE ; - Address RETRIES_QUEUE ; - Address ORIGINAL_QUEUE ; - Address CLIENT_QUEUE ; - - TransportMessage message; - - [SetUp] - public void SetUp() - { - messageSender = new FakeMessageSender(); - deferrer = new FakeMessageDeferrer(); - ERROR_QUEUE = new Address("error", "localhost"); - RETRIES_QUEUE = new Address("retries", "localhost"); - ORIGINAL_QUEUE = new Address("org", "hostname"); - CLIENT_QUEUE = Address.Parse("clientQ@myMachine"); - var busNotifications = new BusNotifications(); - satellite = new SecondLevelRetriesProcessor - { - FaultManager = new FaultManager(null, null, busNotifications) - { - ErrorQueue = ERROR_QUEUE - }, - MessageSender = messageSender, - MessageDeferrer = deferrer, - InputAddress = RETRIES_QUEUE, - SecondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration() - }; - - message = new TransportMessage(Guid.NewGuid().ToString(), new Dictionary{{Headers.ReplyToAddress,CLIENT_QUEUE.ToString()}}); - } - - [Test] - public void Message_should_have_ReplyToAddress_set_to_original_sender_when_sent_to_real_error_queue() - { - satellite.SecondLevelRetriesConfiguration.RetryPolicy = _ => TimeSpan.MinValue; - - satellite.Handle(message); - - Assert.AreEqual(CLIENT_QUEUE, message.ReplyToAddress); - } - - [Test] - public void Message_should_have_ReplyToAddress_set_to_original_sender_when_sent_to_real_error_queue_after_retries() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - - - for (var i = 0; i < satellite.SecondLevelRetriesConfiguration.NumberOfRetries + 1; i++) - { - satellite.Handle(message); - } - - Assert.AreEqual(CLIENT_QUEUE, message.ReplyToAddress); - } - - [Test] - public void Message_should_be_sent_to_real_errorQ_if_defer_timeSpan_is_less_than_zero() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - satellite.SecondLevelRetriesConfiguration.RetryPolicy = _ => TimeSpan.MinValue; - - satellite.Handle(message); - - Assert.AreEqual(ERROR_QUEUE, messageSender.MessageSentTo); - } - - [Test] - public void Message_should_be_sent_to_retryQ_if_defer_timeSpan_is_greater_than_zero() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - satellite.SecondLevelRetriesConfiguration.RetryPolicy = _ => TimeSpan.FromSeconds(1); - - satellite.Handle(message); - - Assert.AreEqual(message, deferrer.DeferredMessage); - } - - [Test] - public void Message_should_only_be_retried_X_times_when_using_the_defaultPolicy() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - - for (var i = 0; i < satellite.SecondLevelRetriesConfiguration.NumberOfRetries + 1; i++) - { - satellite.Handle(message); - } - - Assert.AreEqual(ERROR_QUEUE, messageSender.MessageSentTo); - } - - [Test] - public void Message_retries_header_should_be_removed_before_being_sent_to_real_errorQ() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - - satellite.Handle(message); - - TransportMessageHeaderHelper.SetHeader(message, SecondLevelRetriesHeaders.RetriesTimestamp, DateTimeExtensions.ToWireFormattedString(DateTime.Now.AddDays(-2))); - - satellite.Handle(message); - - Assert.False(message.Headers.ContainsKey(Headers.Retries)); - } - - [Test] - public void A_message_should_only_be_able_to_retry_during_N_minutes() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - TransportMessageHeaderHelper.SetHeader(message, SecondLevelRetriesHeaders.RetriesTimestamp, DateTimeExtensions.ToWireFormattedString(DateTime.Now.AddDays(-2))); - satellite.Handle(message); - - Assert.AreEqual(ERROR_QUEUE, messageSender.MessageSentTo); - } - - [Test] - public void For_each_retry_the_NServiceBus_Retries_header_should_be_increased() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, "reply@address"); - satellite.SecondLevelRetriesConfiguration.RetryPolicy = _ => TimeSpan.FromSeconds(1); - - for (var i = 0; i < 10; i++) - { - satellite.Handle(message); - } - - Assert.AreEqual(10, TransportMessageHeaderHelper.GetNumberOfRetries(message)); - } - - [Test] - public void Message_should_be_routed_to_the_failing_endpoint_when_the_time_is_up() - { - TransportMessageHeaderHelper.SetHeader(message, FaultsHeaderKeys.FailedQ, ORIGINAL_QUEUE.ToString()); - satellite.SecondLevelRetriesConfiguration.RetryPolicy = _ => TimeSpan.FromSeconds(1); - - satellite.Handle(message); - - Assert.AreEqual(ORIGINAL_QUEUE, deferrer.MessageRoutedTo); - } - } - - class FakeMessageDeferrer : IDeferMessages - { - public Address MessageRoutedTo { get; set; } - - public TransportMessage DeferredMessage { get; set; } - - public void Defer(TransportMessage message, SendOptions sendOptions) - { - MessageRoutedTo = sendOptions.Destination; - DeferredMessage = message; - } - - public void ClearDeferredMessages(string headerKey, string headerValue) - { - - } - } - - class FakeMessageSender : ISendMessages - { - public Address MessageSentTo { get; set; } - - public void Send(TransportMessage message, SendOptions sendOptions) - { - MessageSentTo = sendOptions.Destination; - } - } -} diff --git a/src/NServiceBus.Core.Tests/Serializers/Binary/BinarySerializerTest.cs b/src/NServiceBus.Core.Tests/Serializers/Binary/BinarySerializerTest.cs deleted file mode 100644 index 3c4232d1ba4..00000000000 --- a/src/NServiceBus.Core.Tests/Serializers/Binary/BinarySerializerTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace NServiceBus.Core.Tests.Serializers.Binary -{ - using System; - using System.IO; - using System.Linq; - using System.Xml.Linq; - using NServiceBus.Serializers.Binary; - using NUnit.Framework; - - [TestFixture] - public class BinarySerializerTest - { - private BinaryMessageSerializer serializer; - - [SetUp] - public void SetUp() - { - serializer = new BinaryMessageSerializer(); - } - - [Test] - public void When_Using_Property_WithXContainerAssignable_should_serialize() - { - const string XmlElement = ""; - const string XmlDocument = "" + XmlElement; - - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; - - MessageWithXDocument resultXDocument; - using (var stream = new MemoryStream()) - { - serializer.Serialize(messageWithXDocument, stream); - - stream.Position = 0; - - resultXDocument = serializer.Deserialize(stream, new[] { typeof(MessageWithXDocument) }).OfType().Single(); - } - - MessageWithXElement resultXElement; - using (var stream = new MemoryStream()) - { - serializer.Serialize(messageWithXElement, stream); - - stream.Position = 0; - - resultXElement = serializer.Deserialize(stream, new[] { typeof(MessageWithXElement) }).OfType().Single(); - } - - Assert.AreEqual(messageWithXDocument.Document.ToString(), resultXDocument.Document.ToString()); - Assert.AreEqual(messageWithXElement.Document.ToString(), resultXElement.Document.ToString()); - } - - [Serializable] - public class MessageWithXDocument : IMessage - { - public XDocument Document { get; set; } - } - - [Serializable] - public class MessageWithXElement : IMessage - { - public XElement Document { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/Json/BsonMessageSerializerTest.cs b/src/NServiceBus.Core.Tests/Serializers/Json/BsonMessageSerializerTest.cs deleted file mode 100644 index cef347fd222..00000000000 --- a/src/NServiceBus.Core.Tests/Serializers/Json/BsonMessageSerializerTest.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.Serializers.Json.Tests -{ - using System.IO; - using System.Linq; - using System.Xml.Linq; - using NUnit.Framework; - - [TestFixture] - public class BsonMessageSerializerTest : JsonMessageSerializerTestBase - { - [SetUp] - public void Setup() - { - Serializer = new BsonMessageSerializer(MessageMapper); - } - - [Test] - public void When_Using_Property_WithXContainerAssignable_should_serialize() - { - const string XmlElement = ""; - const string XmlDocument = "" + XmlElement; - - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; - - using (var stream = new MemoryStream()) - { - Serializer.Serialize(messageWithXDocument, stream); - - stream.Position = 0; - - var result = Serializer.Deserialize(stream, new[] { typeof(MessageWithXDocument) }).Cast().Single(); - - Assert.AreEqual(messageWithXDocument.Document.ToString(), result.Document.ToString()); - } - - using (var stream = new MemoryStream()) - { - Serializer.Serialize(messageWithXElement, stream); - - stream.Position = 0; - - var result = Serializer.Deserialize(stream, new[] { typeof(MessageWithXElement) }).Cast().Single(); - - Assert.AreEqual(messageWithXElement.Document.ToString(), result.Document.ToString()); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTest.cs b/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTest.cs index 3fa0ee1a8ed..9bef3d87093 100644 --- a/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTest.cs +++ b/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTest.cs @@ -1,39 +1,123 @@ namespace NServiceBus.Serializers.Json.Tests { + using System; + using System.Collections.Generic; using System.IO; using System.Linq; + using System.Reflection; using System.Xml.Linq; using MessageInterfaces.MessageMapper.Reflection; using NUnit.Framework; + using JsonMessageSerializer = NServiceBus.JsonMessageSerializer; [TestFixture] - public class JsonMessageSerializerTest : JsonMessageSerializerTestBase + public class JsonMessageSerializerTest { - public JsonMessageSerializerTest() - : base(typeof(SimpleMessage)) + [Test] + public void Should_handle_concrete_message_with_invalid_interface_property() { - } + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(MessageWithInvalidInterfaceProperty) + }); - [SetUp] - public void Setup() - { - Serializer = new JsonMessageSerializer(MessageMapper); + var serializer = new JsonMessageSerializer(messageMapper); + + var message = new MessageWithInvalidInterfaceProperty + { + InterfaceProperty = new InvalidInterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + var result = (MessageWithInvalidInterfaceProperty) serializer.Deserialize(stream, new[] + { + typeof(MessageWithInvalidInterfaceProperty) + })[0]; + + Assert.AreEqual(message.InterfaceProperty.SomeProperty, result.InterfaceProperty.SomeProperty); + } } - public class SimpleMessage1 + [Test] + public void Should_handle_concrete_message_with_interface_property() { - public string PropertyOnMessage1 { get; set; } + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(MessageWithInterfaceProperty) + }); + var serializer = new JsonMessageSerializer(messageMapper); + + var message = new MessageWithInterfaceProperty + { + InterfaceProperty = new InterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + var result = (MessageWithInterfaceProperty) serializer.Deserialize(stream, new[] + { + typeof(MessageWithInterfaceProperty) + })[0]; + + Assert.AreEqual(message.InterfaceProperty.SomeProperty, result.InterfaceProperty.SomeProperty); + } } - public class SimpleMessage2 + [Test] + public void Should_handle_interface_message_with_interface_property() { - public string PropertyOnMessage2 { get; set; } - } + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(IMessageWithInterfaceProperty) + }); + var serializer = new JsonMessageSerializer(messageMapper); + IMessageWithInterfaceProperty message = new InterfaceMessageWithInterfacePropertyImplementation + { + InterfaceProperty = new InterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + var result = (IMessageWithInterfaceProperty) serializer.Deserialize(stream, new[] + { + typeof(IMessageWithInterfaceProperty) + })[0]; + + Assert.AreEqual(message.InterfaceProperty.SomeProperty, result.InterfaceProperty.SomeProperty); + } + } [Test] public void Deserialize_messages_wrapped_in_array_from_older_endpoint() { + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); var jsonWithMultipleMessages = @" [ { @@ -51,7 +135,7 @@ public void Deserialize_messages_wrapped_in_array_from_older_endpoint() streamWriter.Write(jsonWithMultipleMessages); streamWriter.Flush(); stream.Position = 0; - var result = Serializer.Deserialize(stream, new[] + var result = serializer.Deserialize(stream, new[] { typeof(SimpleMessage2), typeof(SimpleMessage1) @@ -66,13 +150,22 @@ public void Deserialize_messages_wrapped_in_array_from_older_endpoint() [Test] public void Deserialize_message_with_interface_without_wrapping() { + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(new SuperMessage {SomeProperty = "John"}, stream); + serializer.Serialize(new SuperMessage + { + SomeProperty = "John" + }, stream); stream.Position = 0; - var result = (SuperMessage)Serializer.Deserialize(stream, new[] { typeof(SuperMessage), typeof(IMyEvent) })[0]; + var result = (SuperMessage) serializer.Deserialize(stream, new[] + { + typeof(SuperMessage), + typeof(IMyEvent) + })[0]; Assert.AreEqual("John", result.SomeProperty); } @@ -81,9 +174,13 @@ public void Deserialize_message_with_interface_without_wrapping() [Test] public void Deserialize_private_message_with_two_unrelated_interface_without_wrapping() { - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(IMyEventA), typeof(IMyEventB) }); - Serializer = new JsonMessageSerializer(MessageMapper); + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(IMyEventA), + typeof(IMyEventB) + }); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { @@ -93,11 +190,15 @@ public void Deserialize_private_message_with_two_unrelated_interface_without_wra StringValue = "Answer" }; - Serializer.Serialize(msg, stream); + serializer.Serialize(msg, stream); stream.Position = 0; - var result = Serializer.Deserialize(stream, new[] { typeof(IMyEventA), typeof(IMyEventB) }); + var result = serializer.Deserialize(stream, new[] + { + typeof(IMyEventA), + typeof(IMyEventB) + }); var a = (IMyEventA) result[0]; var b = (IMyEventB) result[1]; Assert.AreEqual(42, b.IntValue); @@ -108,9 +209,11 @@ public void Deserialize_private_message_with_two_unrelated_interface_without_wra [Test] public void Serialize_message_without_wrapping() { + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(new SimpleMessage(), stream); + serializer.Serialize(new SimpleMessage(), stream); stream.Position = 0; var result = new StreamReader(stream).ReadToEnd(); @@ -122,24 +225,33 @@ public void Serialize_message_without_wrapping() [Test] public void Deserialize_message_without_wrapping() { + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(new SimpleMessage{SomeProperty = "test"}, stream); + serializer.Serialize(new SimpleMessage + { + SomeProperty = "test" + }, stream); stream.Position = 0; - var result = (SimpleMessage) Serializer.Deserialize(stream, new[]{typeof(SimpleMessage)})[0]; + var result = (SimpleMessage) serializer.Deserialize(stream, new[] + { + typeof(SimpleMessage) + })[0]; - Assert.AreEqual("test",result.SomeProperty); + Assert.AreEqual("test", result.SomeProperty); } - } [Test] public void Serialize_message_without_typeInfo() { + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(new SimpleMessage(), stream); + serializer.Serialize(new SimpleMessage(), stream); stream.Position = 0; var result = new StreamReader(stream).ReadToEnd(); @@ -151,13 +263,16 @@ public void Serialize_message_without_typeInfo() [Test] public void Serialize_message_without_concrete_implementation() { - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(ISuperMessageWithoutConcreteImpl) }); - Serializer = new JsonMessageSerializer(MessageMapper); + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(ISuperMessageWithoutConcreteImpl) + }); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(MessageMapper.CreateInstance(), stream); + serializer.Serialize(messageMapper.CreateInstance(), stream); stream.Position = 0; var result = new StreamReader(stream).ReadToEnd(); @@ -170,20 +285,26 @@ public void Serialize_message_without_concrete_implementation() [Test] public void Deserialize_message_without_concrete_implementation() { - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(ISuperMessageWithoutConcreteImpl) }); - Serializer = new JsonMessageSerializer(MessageMapper); + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(ISuperMessageWithoutConcreteImpl) + }); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - var msg = MessageMapper.CreateInstance(); + var msg = messageMapper.CreateInstance(); msg.SomeProperty = "test"; - Serializer.Serialize(msg, stream); + serializer.Serialize(msg, stream); stream.Position = 0; - var result = (ISuperMessageWithoutConcreteImpl)Serializer.Deserialize(stream, new[] { typeof(ISuperMessageWithoutConcreteImpl) })[0]; + var result = (ISuperMessageWithoutConcreteImpl) serializer.Deserialize(stream, new[] + { + typeof(ISuperMessageWithoutConcreteImpl) + })[0]; Assert.AreEqual("test", result.SomeProperty); } @@ -192,10 +313,14 @@ public void Deserialize_message_without_concrete_implementation() [Test] public void Deserialize_message_with_concrete_implementation_and_interface() { - var map = new[] {typeof(SuperMessageWithConcreteImpl), typeof(ISuperMessageWithConcreteImpl)}; - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(map); - Serializer = new JsonMessageSerializer(MessageMapper); + var map = new[] + { + typeof(SuperMessageWithConcreteImpl), + typeof(ISuperMessageWithConcreteImpl) + }; + var messageMapper = new MessageMapper(); + messageMapper.Initialize(map); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { @@ -204,17 +329,17 @@ public void Deserialize_message_with_concrete_implementation_and_interface() SomeProperty = "test" }; - Serializer.Serialize(msg, stream); + serializer.Serialize(msg, stream); stream.Position = 0; - var result = (ISuperMessageWithConcreteImpl)Serializer.Deserialize(stream, map)[0]; + var result = (ISuperMessageWithConcreteImpl) serializer.Deserialize(stream, map)[0]; Assert.IsInstanceOf(result); Assert.AreEqual("test", result.SomeProperty); } } - + [Test] public void When_Using_Property_WithXContainerAssignable_should_preserve_xml() @@ -222,18 +347,29 @@ public void When_Using_Property_WithXContainerAssignable_should_preserve_xml() const string XmlElement = ""; const string XmlDocument = "" + XmlElement; - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; + var messageWithXDocument = new MessageWithXDocument + { + Document = XDocument.Load(new StringReader(XmlDocument)) + }; + var messageWithXElement = new MessageWithXElement + { + Document = XElement.Load(new StringReader(XmlElement)) + }; + var messageMapper = new MessageMapper(); + var serializer = new JsonMessageSerializer(messageMapper); using (var stream = new MemoryStream()) { - Serializer.Serialize(messageWithXDocument, stream); + serializer.Serialize(messageWithXDocument, stream); stream.Position = 0; var json = new StreamReader(stream).ReadToEnd(); stream.Position = 0; - var result = Serializer.Deserialize(stream, new[] { typeof(MessageWithXDocument) }).Cast().Single(); + var result = serializer.Deserialize(stream, new[] + { + typeof(MessageWithXDocument) + }).Cast().Single(); Assert.AreEqual(messageWithXDocument.Document.ToString(), result.Document.ToString()); Assert.AreEqual(XmlElement, json.Substring(13, json.Length - 15).Replace("\\", string.Empty)); @@ -241,22 +377,181 @@ public void When_Using_Property_WithXContainerAssignable_should_preserve_xml() using (var stream = new MemoryStream()) { - Serializer.Serialize(messageWithXElement, stream); + serializer.Serialize(messageWithXElement, stream); stream.Position = 0; var json = new StreamReader(stream).ReadToEnd(); stream.Position = 0; - var result = Serializer.Deserialize(stream, new[] { typeof(MessageWithXElement) }).Cast().Single(); + var result = serializer.Deserialize(stream, new[] + { + typeof(MessageWithXElement) + }).Cast().Single(); Assert.AreEqual(messageWithXElement.Document.ToString(), result.Document.ToString()); Assert.AreEqual(XmlElement, json.Substring(13, json.Length - 15).Replace("\\", string.Empty)); } } + [Test] + public void Test() + { + var expectedDate = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Unspecified); + var expectedDateLocal = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Local); + var expectedDateUtc = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Utc); + var expectedGuid = Guid.NewGuid(); + + var obj = new A + { + AGuid = expectedGuid, + Data = new byte[32], + I = 23, + S = "Foo", + Ints = new List + { + 12, + 42 + }, + Bs = new List + { + new B + { + BString = "aaa", + C = new C + { + Cstr = "ccc" + } + }, + new BB + { + BString = "bbbb", + C = new C + { + Cstr = "dddd" + }, + BBString = "BBStr" + } + }, + DateTime = expectedDate, + DateTimeLocal = expectedDateLocal, + DateTimeUtc = expectedDateUtc + }; + + new Random().NextBytes(obj.Data); + + var output = new MemoryStream(); + + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(IA), + typeof(A) + }); + var serializer = new JsonMessageSerializer(messageMapper); + + serializer.Serialize(obj, output); + + output.Position = 0; + + var result = serializer.Deserialize(output, new[] + { + typeof(A) + }); + + Assert.DoesNotThrow(() => output.Position = 0, "Stream should still be open"); + + Assert.That(result[0], Is.TypeOf(typeof(A))); + var a = (A) result[0]; + + Assert.AreEqual(obj.Data, a.Data); + Assert.AreEqual(23, a.I); + Assert.AreEqual("Foo", a.S); + Assert.AreEqual(expectedDate.Kind, a.DateTime.Kind); + Assert.AreEqual(expectedDate, a.DateTime); + Assert.AreEqual(expectedDateLocal.Kind, a.DateTimeLocal.Kind); + Assert.AreEqual(expectedDateLocal, a.DateTimeLocal); + Assert.AreEqual(expectedDateUtc.Kind, a.DateTimeUtc.Kind); + Assert.AreEqual(expectedDateUtc, a.DateTimeUtc); + Assert.AreEqual("ccc", ((C) a.Bs[0].C).Cstr); + Assert.AreEqual(expectedGuid, a.AGuid); + + Assert.IsInstanceOf(a.Bs[0]); + Assert.IsInstanceOf(a.Bs[1]); + } + + [Test] + public void TestInterfaces() + { + var output = new MemoryStream(); + + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(IA), + typeof(IAImpl) + }); + var obj = messageMapper.CreateInstance( + x => + { + x.S = "kalle"; + x.I = 42; + x.Data = new byte[23]; + x.B = new B + { + BString = "BOO", + C = new C + { + Cstr = "COO" + } + }; + } + ); + + new Random().NextBytes(obj.Data); + + var serializer = new JsonMessageSerializer(messageMapper); + + serializer.Serialize(obj, output); + + output.Position = 0; + + var filename = $"{GetType().Name}.{MethodBase.GetCurrentMethod().Name}.txt"; + + File.WriteAllBytes(filename, output.ToArray()); + + output.Position = 0; + + var result = serializer.Deserialize(output, new[] + { + typeof(IAImpl) + }); + + Assert.DoesNotThrow(() => output.Position = 0, "Stream should still be open"); + + Assert.IsNotEmpty(result); + Assert.That(result, Has.Length.EqualTo(1)); + + Assert.That(result[0], Is.AssignableTo(typeof(IA))); + var a = (IA) result[0]; + + Assert.AreEqual(a.Data, obj.Data); + Assert.AreEqual(42, a.I); + Assert.AreEqual("kalle", a.S); + Assert.IsNotNull(a.B); + Assert.AreEqual("BOO", a.B.BString); + Assert.AreEqual("COO", ((C) a.B.C).Cstr); + } + [Test] public void TestMany() { + var messageMapper = new MessageMapper(); + messageMapper.Initialize(new[] + { + typeof(IAImpl), + typeof(IA) + }); + var serializer = new JsonMessageSerializer(messageMapper); var xml = @"[{ $type: ""NServiceBus.Serializers.Json.Tests.IA, NServiceBus.Core.Tests"", Data: ""rhNAGU4dr/Qjz6ocAsOs3wk3ZmxHMOg="", @@ -289,16 +584,16 @@ public void TestMany() streamWriter.Flush(); stream.Position = 0; - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(IAImpl), typeof(IA) }); - Serializer = new JsonMessageSerializer(MessageMapper); - var result = Serializer.Deserialize(stream, new[] { typeof(IAImpl) }); + var result = serializer.Deserialize(stream, new[] + { + typeof(IAImpl) + }); Assert.IsNotEmpty(result); Assert.That(result, Has.Length.EqualTo(2)); Assert.That(result[0], Is.AssignableTo(typeof(IA))); - var a = ((IA) result[0]); + var a = (IA) result[0]; Assert.AreEqual(23, a.Data.Length); Assert.AreEqual(42, a.I); @@ -307,7 +602,16 @@ public void TestMany() Assert.AreEqual("BOO", a.B.BString); Assert.AreEqual("COO", ((C) a.B.C).Cstr); } + } + public class SimpleMessage1 + { + public string PropertyOnMessage1 { get; set; } + } + + public class SimpleMessage2 + { + public string PropertyOnMessage2 { get; set; } } } @@ -315,6 +619,7 @@ public class SimpleMessage { public string SomeProperty { get; set; } } + public class SuperMessage : IMyEvent { public string SomeProperty { get; set; } @@ -374,4 +679,98 @@ public class SuperMessageWithConcreteImpl : ISuperMessageWithConcreteImpl { public string SomeProperty { get; set; } } -} + + + public class B + { + public string BString { get; set; } + public object C { get; set; } + } + + public class BB : B + { + public string BBString { get; set; } + } + + public class C + { + public string Cstr { get; set; } + } + + public class IAImpl : IA + { + public byte[] Data { get; set; } + public string S { get; set; } + public int I { get; set; } + public B B { get; set; } + } + + public class A : IMessage + { + public Guid AGuid { get; set; } + public int I { get; set; } + + public DateTime DateTime { get; set; } + public DateTime DateTimeLocal { get; set; } + public DateTime DateTimeUtc { get; set; } + + public List Ints { get; set; } + public List Bs { get; set; } + public byte[] Data; + public string S; + } + + public interface IA : IMessage + { + byte[] Data { get; set; } + string S { get; set; } + int I { get; set; } + B B { get; set; } + } + + class MessageWithInvalidInterfaceProperty + { + public IInvalidInterfaceProperty InterfaceProperty { get; set; } + } + + public interface IInvalidInterfaceProperty + { + string SomeProperty { get; set; } + + void SomeMethod(); + } + + class InvalidInterfacePropertyImplementation : IInvalidInterfaceProperty + { + public string SomeProperty { get; set; } + + public void SomeMethod() + { + } + } + + class MessageWithInterfaceProperty + { + public IInterfaceProperty InterfaceProperty { get; set; } + } + + public interface IInterfaceProperty + { + string SomeProperty { get; set; } + } + + class InterfacePropertyImplementation : IInterfaceProperty + { + public string SomeProperty { get; set; } + } + + public interface IMessageWithInterfaceProperty + { + IInterfaceProperty InterfaceProperty { get; set; } + } + + class InterfaceMessageWithInterfacePropertyImplementation : IMessageWithInterfaceProperty + { + public IInterfaceProperty InterfaceProperty { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTestBase.cs b/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTestBase.cs deleted file mode 100644 index 16321489c4b..00000000000 --- a/src/NServiceBus.Core.Tests/Serializers/Json/JsonMessageSerializerTestBase.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace NServiceBus.Serializers.Json.Tests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using MessageInterfaces.MessageMapper.Reflection; - using NUnit.Framework; - - public class A : IMessage - { - public Guid AGuid { get; set; } - public byte[] Data; - public string S; - public int I { get; set; } - - public DateTime DateTime { get; set; } - public DateTime DateTimeLocal { get; set; } - public DateTime DateTimeUtc { get; set; } - - public List Ints { get; set; } - public List Bs { get; set; } - - } - - public interface IA : IMessage - { - byte[] Data { get; set; } - string S { get; set; } - int I { get; set; } - B B { get; set; } - } - public class IAImpl : IA - { - public byte[] Data { get; set; } - public string S { get; set; } - public int I { get; set; } - public B B { get; set; } - } - - public class B - { - public string BString { get; set; } - public object C { get; set; } - } - - public class BB : B - { - public string BBString { get; set; } - } - - public class C - { - public string Cstr { get; set; } - } - - - - public abstract class JsonMessageSerializerTestBase - { - protected JsonMessageSerializerBase Serializer { get; set; } - protected MessageMapper MessageMapper { get; set; } - - protected JsonMessageSerializerTestBase(params Type[] messageTypes) - { - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(IA), typeof(A) }.Union(messageTypes)); - } - - [Test] - public void Test() - { - var expectedDate = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Unspecified); - var expectedDateLocal = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Local); - var expectedDateUtc = new DateTime(2010, 10, 13, 12, 32, 42, DateTimeKind.Utc); - var expectedGuid = Guid.NewGuid(); - - var obj = new A - { - AGuid = expectedGuid, - Data = new byte[32], - I = 23, - S = "Foo", - Ints = new List { 12, 42 }, - Bs = new List { new B { BString = "aaa", C = new C { Cstr = "ccc" } }, new BB { BString = "bbbb", C = new C { Cstr = "dddd" }, BBString = "BBStr"} }, - DateTime = expectedDate, - DateTimeLocal = expectedDateLocal, - DateTimeUtc = expectedDateUtc - }; - - new Random().NextBytes(obj.Data); - - var output = new MemoryStream(); - - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(IA), typeof(A) }); - Serializer = new JsonMessageSerializer(MessageMapper); - - Serializer.Serialize(obj, output); - - output.Position = 0; - - var result = Serializer.Deserialize(output, new[] { typeof(A) }); - - Assert.DoesNotThrow(() => output.Position = 0, "Stream should still be open"); - - Assert.That(result[0], Is.TypeOf(typeof(A))); - var a = ((A)result[0]); - - Assert.AreEqual(obj.Data, a.Data); - Assert.AreEqual(23, a.I); - Assert.AreEqual("Foo", a.S); - Assert.AreEqual(expectedDate.Kind, a.DateTime.Kind); - Assert.AreEqual(expectedDate, a.DateTime); - Assert.AreEqual(expectedDateLocal.Kind, a.DateTimeLocal.Kind); - Assert.AreEqual(expectedDateLocal, a.DateTimeLocal); - Assert.AreEqual(expectedDateUtc.Kind, a.DateTimeUtc.Kind); - Assert.AreEqual(expectedDateUtc, a.DateTimeUtc); - Assert.AreEqual("ccc", ((C)a.Bs[0].C).Cstr); - Assert.AreEqual(expectedGuid, a.AGuid); - - Assert.IsInstanceOf(a.Bs[0]); - Assert.IsInstanceOf(a.Bs[1]); - } - - [Test] - public void TestInterfaces() - { - var output = new MemoryStream(); - - var obj = MessageMapper.CreateInstance( - x => - { - x.S = "kalle"; - x.I = 42; - x.Data = new byte[23]; - x.B = new B { BString = "BOO", C = new C { Cstr = "COO" } }; - } - ); - - new Random().NextBytes(obj.Data); - - MessageMapper = new MessageMapper(); - MessageMapper.Initialize(new[] { typeof(IA), typeof(IAImpl) }); - Serializer = new JsonMessageSerializer(MessageMapper); - - Serializer.Serialize(obj, output); - - output.Position = 0; - - var filename = string.Format("{0}.{1}.txt", GetType().Name, MethodBase.GetCurrentMethod().Name); - - File.WriteAllBytes(filename, output.ToArray()); - - output.Position = 0; - - var result = Serializer.Deserialize(output, new[] { typeof(IAImpl) }); - - Assert.DoesNotThrow(() => output.Position = 0, "Stream should still be open"); - - Assert.IsNotEmpty(result); - Assert.That(result, Has.Length.EqualTo(1)); - - Assert.That(result[0], Is.AssignableTo(typeof(IA))); - var a = ((IA)result[0]); - - Assert.AreEqual(a.Data, obj.Data); - Assert.AreEqual(42, a.I); - Assert.AreEqual("kalle", a.S); - Assert.IsNotNull(a.B); - Assert.AreEqual("BOO", a.B.BString); - Assert.AreEqual("COO", ((C)a.B.C).Cstr); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/Json/When_not_overriding_stream_encoding.cs b/src/NServiceBus.Core.Tests/Serializers/Json/When_not_overriding_stream_encoding.cs index da52abe2e40..ed5aa2a42b3 100644 --- a/src/NServiceBus.Core.Tests/Serializers/Json/When_not_overriding_stream_encoding.cs +++ b/src/NServiceBus.Core.Tests/Serializers/Json/When_not_overriding_stream_encoding.cs @@ -1,28 +1,21 @@ namespace NServiceBus.Serializers.Json.Tests { - using System; using System.Text; - using Features; + using MessageInterfaces.MessageMapper.Reflection; + using Settings; using NUnit.Framework; [TestFixture] public class When_not_overriding_stream_encoding { - + [Test] public void Should_construct_serializer_that_uses_default_encoding() { - var builder = new BusConfiguration(); - - builder.TypesToScan(new Type[0]); - builder.UseSerialization(); + var settings = new SettingsHolder(); - var config = builder.BuildConfiguration(); + var serializer = (NServiceBus.JsonMessageSerializer)new JsonSerializer().Configure(settings)(new MessageMapper()); - var context = new FeatureConfigurationContext(config); - new JsonSerialization().SetupFeature(context); - - var serializer = config.Builder.Build(); Assert.AreSame(Encoding.UTF8, serializer.Encoding); } } diff --git a/src/NServiceBus.Core.Tests/Serializers/Json/When_overriding_stream_encoding.cs b/src/NServiceBus.Core.Tests/Serializers/Json/When_overriding_stream_encoding.cs index 044b5838434..2a16d7caf32 100644 --- a/src/NServiceBus.Core.Tests/Serializers/Json/When_overriding_stream_encoding.cs +++ b/src/NServiceBus.Core.Tests/Serializers/Json/When_overriding_stream_encoding.cs @@ -1,8 +1,9 @@ namespace NServiceBus.Serializers.Json.Tests { - using System; using System.Text; - using Features; + using MessageInterfaces.MessageMapper.Reflection; + using Serialization; + using Settings; using NUnit.Framework; [TestFixture] @@ -11,17 +12,11 @@ public class When_overriding_stream_encoding [Test] public void Should_construct_serializer_that_uses_requested_encoding() { - var builder = new BusConfiguration(); + var settings = new SettingsHolder(); + var extensions = new SerializationExtensions(settings); + extensions.Encoding(Encoding.UTF7); - builder.TypesToScan(new Type[0]); - builder.UseSerialization().Encoding(Encoding.UTF7); - - var config = builder.BuildConfiguration(); - - var context = new FeatureConfigurationContext(config); - new JsonSerialization().SetupFeature(context); - - var serializer = config.Builder.Build(); + var serializer = (NServiceBus.JsonMessageSerializer)new JsonSerializer().Configure(settings)(new MessageMapper()); Assert.AreSame(Encoding.UTF7, serializer.Encoding); } } diff --git a/src/NServiceBus.Core.Tests/Serializers/MessageDeserializerResolverTests.cs b/src/NServiceBus.Core.Tests/Serializers/MessageDeserializerResolverTests.cs new file mode 100644 index 00000000000..53d4f5ec443 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Serializers/MessageDeserializerResolverTests.cs @@ -0,0 +1,96 @@ +namespace NServiceBus.Core.Tests.Serializers +{ + using System; + using System.Collections.Generic; + using System.IO; + using Serialization; + using NUnit.Framework; + + [TestFixture] + public class MessageDeserializerResolverTests + { + [TestCase(ContentTypes.Xml)] + [TestCase(ContentTypes.Json)] + public void RetrievesSerializerByContentType(string contentType) + { + var expectedResolver = new FakeSerializer(contentType); + var resolver = new MessageDeserializerResolver(new FakeSerializer("default"), new IMessageSerializer[] + { + new FakeSerializer("some/content/type"), + expectedResolver, + new FakeSerializer("another/content/type") + }); + + var headers = new Dictionary + { + {Headers.ContentType, contentType} + }; + var serializer = resolver.Resolve(headers); + Assert.AreSame(expectedResolver, serializer); + } + + [Test] + public void UnknownContentTypeFallsBackToDefaultSerialization() + { + var defaultSerializer = new FakeSerializer(ContentTypes.Xml); + var resolver = new MessageDeserializerResolver(defaultSerializer, new IMessageSerializer[] + { + new FakeSerializer(ContentTypes.Json) + }); + + var headers = new Dictionary + { + {Headers.ContentType, "unknown/unsupported"} + }; + var serializer = resolver.Resolve(headers); + + Assert.AreSame(defaultSerializer, serializer); + } + + [Test] + public void NoContentTypeFallsBackToDefaultSerialization() + { + var defaultSerializer = new FakeSerializer(ContentTypes.Xml); + var resolver = new MessageDeserializerResolver(defaultSerializer, new IMessageSerializer[] + { + new FakeSerializer(ContentTypes.Json) + }); + + var serializer = resolver.Resolve(new Dictionary()); + + Assert.AreEqual(defaultSerializer, serializer); + } + + [Test] + public void MultipleDeserializersWithSameContentTypeShouldThrowException() + { + var deserializer1 = new FakeSerializer("my/content/type"); + var deserializer2 = new FakeSerializer("my/content/type"); + + Assert.That(() => new MessageDeserializerResolver(new FakeSerializer("xml"), new IMessageSerializer[] + { + deserializer1, + deserializer2 + }), Throws.Exception.TypeOf().And.Message.Contains($"Multiple deserializers are registered for content-type '{deserializer1.ContentType}'. Remove ambiguous deserializers.")); + } + + class FakeSerializer : IMessageSerializer + { + public FakeSerializer(string contentType) + { + ContentType = contentType; + } + + public void Serialize(object message, Stream stream) + { + } + + public object[] Deserialize(Stream stream, IList messageTypes = null) + { + throw new NotImplementedException(); + } + + public string ContentType { get; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/IM2.cs b/src/NServiceBus.Core.Tests/Serializers/XML/IM2.cs index 5416b943153..c28f7fe1657 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/IM2.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/IM2.cs @@ -20,7 +20,7 @@ public interface IM2 : IM1 Foo[] ArrayFoos { get; set; } Bar[] Bars { get; set; } HashSet NaturalNumbers { get; set; } - HashSet Developers { get; set; } + HashSet Developers { get; set; } } public class Foo @@ -37,6 +37,6 @@ public class Bar public class MyDictionary : Dictionary { - + } } diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/Issue_2796.cs b/src/NServiceBus.Core.Tests/Serializers/XML/Issue_2796.cs deleted file mode 100644 index 5a3a5bda4ee..00000000000 --- a/src/NServiceBus.Core.Tests/Serializers/XML/Issue_2796.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NServiceBus.Core.Tests.Serializers.XML -{ - using System; - using System.IO; - using NServiceBus.Serializers.XML.Test; - using NUnit.Framework; - - [TestFixture] - public class Issue_2796 - { - [Test] - public void Object_property_with_primitive_or_struct_value_should_serialize_correctly() - { - var serializer = SerializerFactory.Create(); - var message = new SerializedPair - { - Key = "AddressId", - Value = new Guid("{ebdeeb33-baa7-4100-b1aa-eb4d6816fd3d}") - }; - - object[] messageDeserialized; - using (Stream stream = new MemoryStream()) - { - serializer.Serialize(message, stream); - - stream.Position = 0; - - messageDeserialized = serializer.Deserialize(stream, new[] { message.GetType() }); - } - - Assert.AreEqual(message.Key, ((SerializedPair)messageDeserialized[0]).Key); - Assert.AreEqual(message.Value, ((SerializedPair)messageDeserialized[0]).Value); - } - - public class SerializedPair - { - public string Key { get; set; } - public object Value { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/M1.cs b/src/NServiceBus.Core.Tests/Serializers/XML/M1.cs index 61583986486..b52d93f336c 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/M1.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/M1.cs @@ -23,7 +23,7 @@ public string this[float key] get { return lookup_float_string[key]; } set { lookup_float_string[key] = value; } } - private Dictionary lookup_int_string = new Dictionary(); - private Dictionary lookup_float_string = new Dictionary(); + Dictionary lookup_int_string = new Dictionary(); + Dictionary lookup_float_string = new Dictionary(); } } diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/MultipleInterfaces.cs b/src/NServiceBus.Core.Tests/Serializers/XML/MultipleInterfaces.cs index dbc44e670f3..80400b3bb2c 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/MultipleInterfaces.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/MultipleInterfaces.cs @@ -1,10 +1,8 @@ namespace NServiceBus.Serializers.XML.Test { - using System; - public interface IFirst : IMessage { - String FirstName { get; set; } + string FirstName { get; set; } } public interface ISecond : IFirst diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/Pull_819.cs b/src/NServiceBus.Core.Tests/Serializers/XML/Pull_819.cs index 87e8cc6bca6..e97d7d9f7cc 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/Pull_819.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/Pull_819.cs @@ -15,7 +15,7 @@ public void Should_check_for_ignore_attribute_before_checking_type() var result = ExecuteSerializer.ForMessage(m3 => { m3.FirstName = "John"; - m3.LastName = "Simons"; + m3.LastName = "Smith"; m3.List = new ArrayList(); m3.GenericList = new List(); }); diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/SerializerFactory.cs b/src/NServiceBus.Core.Tests/Serializers/XML/SerializerFactory.cs index 840eebdedd2..100d9cb5e39 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/SerializerFactory.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/SerializerFactory.cs @@ -5,40 +5,44 @@ namespace NServiceBus.Serializers.XML.Test using System.IO; using System.Xml; using MessageInterfaces.MessageMapper.Reflection; + using XmlMessageSerializer = NServiceBus.XmlMessageSerializer; - public class SerializerFactory + class SerializerFactory { - public static XmlMessageSerializer Create(MessageMapper mapper = null) - { - var types = new List {typeof (T)}; - if (mapper == null) - { - mapper = new MessageMapper(); - } + public static XmlMessageSerializer Create(MessageMapper mapper = null) + { + var types = new List + { + typeof(T) + }; + if (mapper == null) + { + mapper = new MessageMapper(); + } - mapper.Initialize(types); - var serializer = new XmlMessageSerializer(mapper, new Conventions()); + mapper.Initialize(types); + var serializer = new XmlMessageSerializer(mapper, new Conventions()); - serializer.Initialize(types); + serializer.Initialize(types); - return serializer; - } + return serializer; + } - public static XmlMessageSerializer Create(params Type[] types) - { - var mapper = new MessageMapper(); - mapper.Initialize(types); - var serializer = new XmlMessageSerializer(mapper, new Conventions()); + public static XmlMessageSerializer Create(params Type[] types) + { + var mapper = new MessageMapper(); + mapper.Initialize(types); + var serializer = new XmlMessageSerializer(mapper, new Conventions()); - serializer.Initialize(types); + serializer.Initialize(types); - return serializer; - } + return serializer; + } } public class ExecuteSerializer { - public static T ForMessage(Action a) where T : class,new() + public static T ForMessage(Action a) where T : class, new() { var msg = new T(); a(msg); @@ -52,26 +56,24 @@ public static T ForMessage(object message) { SerializerFactory.Create().Serialize(message, stream); stream.Position = 0; - - var msgArray = SerializerFactory.Create().Deserialize(stream, new[]{message.GetType()}); - return (T)msgArray[0]; + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] + { + message.GetType() + }); + return (T) msgArray[0]; } } - } - public class Serializer + class Serializer { - string xmlResult; - XmlDocument xmlDocument; - Serializer(string result) { xmlResult = result; } - public static Serializer Serialize(Action a) where T : class,new() + public static Serializer Serialize(Action a) where T : class, new() { var msg = new T(); a(msg); @@ -79,14 +81,13 @@ public static Serializer Serialize(Action a) where T : class,new() return ForMessage(msg); } - public static Serializer ForMessage(object message,Action config = null) + public static Serializer ForMessage(object message, Action config = null) { using (var stream = new MemoryStream()) { var serializer = SerializerFactory.Create(); - - if(config != null) - config(serializer); + + config?.Invoke(serializer); serializer.Serialize(message, stream); @@ -99,7 +100,7 @@ public static Serializer ForMessage(object message,Action check, string message) { - if(xmlDocument == null) + if (xmlDocument == null) { xmlDocument = new XmlDocument(); @@ -109,16 +110,16 @@ public Serializer AssertResultingXml(Func check, string messa } catch (Exception ex) { - - throw new Exception("Failed to parse xml: " + xmlResult,ex); + throw new Exception("Failed to parse xml: " + xmlResult, ex); } - - } if (!check(xmlDocument)) - throw new Exception(string.Format("{0}, Offending XML: {1}",message, xmlResult)); + throw new Exception($"{message}, Offending XML: {xmlResult}"); return this; } + + string xmlResult; + XmlDocument xmlDocument; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/SerializerTests.cs b/src/NServiceBus.Core.Tests/Serializers/XML/SerializerTests.cs index 2cc8f5442d7..9eda78bb42e 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/SerializerTests.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/SerializerTests.cs @@ -14,6 +14,7 @@ namespace NServiceBus.Serializers.XML.Test using System.Xml; using System.Xml.Linq; using A; + using AlternateNamespace; using B; using MessageInterfaces; using MessageInterfaces.MessageMapper.Reflection; @@ -23,9 +24,6 @@ namespace NServiceBus.Serializers.XML.Test [TestFixture] public class SerializerTests { - private int number = 1; - private int numberOfIterations = 100; - [Test] public void SerializeInvalidCharacters() { @@ -50,15 +48,105 @@ public void SerializeInvalidCharacters() } } + [Test] //note: This is not a desired behavior, but this test documents this limitation + public void Limitation_Does_not_handle_types_implementing_ISerializable() + { + var message = new MessageImplementingISerializable("test"); + + var serializer = SerializerFactory.Create(); + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + stream.Position = 0; + + var result = (MessageImplementingISerializable)serializer.Deserialize(stream)[0]; + + Assert.Null(result.ReadOnlyProperty); + } + } + + [Test] //note: This is not a desired behavior, but this test documents this limitation + public void Limitation_Does_not_handle_concrete_message_with_invalid_interface_property() + { + var message = new MessageWithInvalidInterfaceProperty + { + InterfaceProperty = new InvalidInterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + var serializer = SerializerFactory.Create(); + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + stream.Position = 0; + + Assert.Throws(() => serializer.Deserialize(stream)); + } + } + + [Test] + public void Should_handle_concrete_message_with_interface_property() + { + var message = new MessageWithInterfaceProperty + { + InterfaceProperty = new InterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + var serializer = SerializerFactory.Create(); + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + var result = (MessageWithInterfaceProperty)serializer.Deserialize(stream)[0]; + + Assert.AreEqual(message.InterfaceProperty.SomeProperty, result.InterfaceProperty.SomeProperty); + } + } + + [Test] + public void Should_handle_interface_message_with_interface_property() + { + IMessageWithInterfaceProperty message = new InterfaceMessageWithInterfacePropertyImplementation + { + InterfaceProperty = new InterfacePropertyImplementation + { + SomeProperty = "test" + } + }; + var serializer = SerializerFactory.Create(); + + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + var result = (IMessageWithInterfaceProperty)serializer.Deserialize(stream, new[] + { + typeof(IMessageWithInterfaceProperty) + })[0]; + + Assert.AreEqual(message.InterfaceProperty.SomeProperty, result.InterfaceProperty.SomeProperty); + } + } + [Test, Ignore("ArrayList is not supported")] public void Should_deserialize_arrayList() { var expected = new ArrayList - { - "Value1", - "Value2", - "Value3", - }; + { + "Value1", + "Value2", + "Value3" + }; var result = ExecuteSerializer.ForMessage(m3 => m3.ArrayList = expected); CollectionAssert.AreEqual(expected, result.ArrayList); @@ -68,11 +156,11 @@ public void Should_deserialize_arrayList() public void Should_deserialize_hashtable() { var expected = new Hashtable - { - {"Key1", "Value1"}, - {"Key2", "Value2"}, - {"Key3", "Value3"}, - }; + { + {"Key1", "Value1"}, + {"Key2", "Value2"}, + {"Key3", "Value3"} + }; var result = ExecuteSerializer.ForMessage(m3 => m3.Hashtable = expected); CollectionAssert.AreEqual(expected, result.Hashtable); @@ -82,10 +170,10 @@ public void Should_deserialize_hashtable() public void Should_deserialize_multiple_messages_from_different_namespaces() { var xml = @" - 1eb17e5d-8573-49af-a5cb-76b4a602bb79 @@ -121,7 +209,6 @@ public void Should_deserialize_a_single_message_where_root_element_is_the_typeNa var msgArray = SerializerFactory.Create(typeof(MessageWithDouble)).Deserialize(stream); Assert.AreEqual(typeof(MessageWithDouble), msgArray[0].GetType()); - } } @@ -143,7 +230,11 @@ public void Deserialize_private_message_with_two_unrelated_interface_without_wra stream.Position = 0; - var result = deserializer.Deserialize(stream, new[] { typeof(IMyEventA), typeof(IMyEventB) }); + var result = deserializer.Deserialize(stream, new[] + { + typeof(IMyEventA), + typeof(IMyEventB) + }); var a = (IMyEventA)result[0]; var b = (IMyEventB)result[1]; Assert.AreEqual(42, b.IntValue); @@ -155,7 +246,7 @@ public void Deserialize_private_message_with_two_unrelated_interface_without_wra public void Should_be_able_to_serialize_single_message_without_wrapping_element() { Serializer.ForMessage(new EmptyMessage()) - .AssertResultingXml(d=> d.DocumentElement.Name == "EmptyMessage","Root should be message typename"); + .AssertResultingXml(d => d.DocumentElement.Name == "EmptyMessage", "Root should be message typename"); } [Test] @@ -164,15 +255,19 @@ public void Should_be_able_to_serialize_single_message_without_wrapping_xml_raw_ const string XmlElement = ""; const string XmlDocument = "" + XmlElement; - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; + var messageWithXDocument = new MessageWithXDocument + { + Document = XDocument.Load(new StringReader(XmlDocument)) + }; + var messageWithXElement = new MessageWithXElement + { + Document = XElement.Load(new StringReader(XmlElement)) + }; - Serializer.ForMessage(messageWithXDocument, s => - { s.SkipWrappingRawXml = true; }) + Serializer.ForMessage(messageWithXDocument, s => { s.SkipWrappingRawXml = true; }) .AssertResultingXml(d => d.DocumentElement.ChildNodes[0].FirstChild.Name != "Document", "Property name should not be available"); - Serializer.ForMessage(messageWithXElement, s => - { s.SkipWrappingRawXml = true; }) + Serializer.ForMessage(messageWithXElement, s => { s.SkipWrappingRawXml = true; }) .AssertResultingXml(d => d.DocumentElement.ChildNodes[0].FirstChild.Name != "Document", "Property name should not be available"); } @@ -182,8 +277,14 @@ public void Should_be_able_to_deserialize_messages_which_xml_raw_data_root_eleme const string XmlElement = ""; const string XmlDocument = "" + XmlElement; - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; + var messageWithXDocument = new MessageWithXDocument + { + Document = XDocument.Load(new StringReader(XmlDocument)) + }; + var messageWithXElement = new MessageWithXElement + { + Document = XElement.Load(new StringReader(XmlElement)) + }; var serializer = SerializerFactory.Create(); serializer.SkipWrappingRawXml = true; @@ -193,7 +294,7 @@ public void Should_be_able_to_deserialize_messages_which_xml_raw_data_root_eleme serializer.Serialize(messageWithXDocument, stream); stream.Position = 0; - serializer = SerializerFactory.Create(typeof (MessageWithXDocument)); + serializer = SerializerFactory.Create(typeof(MessageWithXDocument)); serializer.SkipWrappingRawXml = true; var msg = serializer.Deserialize(stream).Cast().Single(); @@ -210,7 +311,7 @@ public void Should_be_able_to_deserialize_messages_which_xml_raw_data_root_eleme serializer.Serialize(messageWithXElement, stream); stream.Position = 0; - serializer = SerializerFactory.Create(typeof (MessageWithXElement)); + serializer = SerializerFactory.Create(typeof(MessageWithXElement)); serializer.SkipWrappingRawXml = true; var msg = serializer.Deserialize(stream).Cast().Single(); @@ -243,7 +344,7 @@ public void Should_be_able_to_serialize_single_message_with_default_namespaces() var serializer = SerializerFactory.Create(); var msg = new EmptyMessage(); - var expected = @""; + var expected = @""; AssertSerializedEquals(serializer, msg, expected); } @@ -255,7 +356,7 @@ public void Should_be_able_to_serialize_single_message_with_specified_namespaces serializer.Namespace = "http://super.com"; var msg = new EmptyMessage(); - var expected = @""; + var expected = @""; AssertSerializedEquals(serializer, msg, expected); } @@ -267,7 +368,7 @@ public void Should_be_able_to_serialize_single_message_with_specified_namespace_ serializer.Namespace = "http://super.com///"; var msg = new EmptyMessage(); - var expected = @""; + var expected = @""; AssertSerializedEquals(serializer, msg, expected); } @@ -282,8 +383,7 @@ static void AssertSerializedEquals(IMessageSerializer serializer, IMessage msg, string result; using (var reader = new StreamReader(stream)) { - reader.ReadLine(); - result = reader.ReadLine(); + result = XDocument.Load(reader).ToString(); } Assert.AreEqual(expected, result); @@ -300,10 +400,32 @@ public void Should_deserialize_a_single_message_with_typeName_passed_in_external writer.Flush(); stream.Position = 0; - var msgArray = SerializerFactory.Create(typeof(MessageWithDouble)).Deserialize(stream, new[] { typeof(MessageWithDouble) }); + var msgArray = SerializerFactory.Create(typeof(MessageWithDouble)).Deserialize(stream, new[] + { + typeof(MessageWithDouble) + }); Assert.AreEqual(typeof(MessageWithDouble), msgArray[0].GetType()); + } + } + [Test] + public void Should_deserialize_a_single_message_with_typeName_passed_in_externally_even_when_not_initialized_with_type() + { + using (var stream = new MemoryStream()) + { + var writer = new StreamWriter(stream); + writer.WriteLine("23.4"); + writer.Flush(); + stream.Position = 0; + + var msgArray = SerializerFactory.Create() + .Deserialize(stream, new[] + { + typeof(MessageWithDouble) + }); + + Assert.AreEqual(23.4, ((MessageWithDouble)msgArray[0]).Double); } } @@ -317,12 +439,37 @@ public void Should_deserialize_a_batched_messages_with_typeName_passed_in_extern writer.Flush(); stream.Position = 0; - var msgArray = SerializerFactory.Create(typeof(MessageWithDouble),typeof(EmptyMessage)) - .Deserialize(stream, new[] { typeof(MessageWithDouble), typeof(EmptyMessage) }); + var msgArray = SerializerFactory.Create(typeof(MessageWithDouble), typeof(EmptyMessage)) + .Deserialize(stream, new[] + { + typeof(MessageWithDouble), + typeof(EmptyMessage) + }); Assert.AreEqual(23.4, ((MessageWithDouble)msgArray[0]).Double); Assert.AreEqual(typeof(EmptyMessage), msgArray[1].GetType()); + } + } + + [Test] + public void Should_deserialize_a_batched_messages_with_typeName_passed_in_externally_even_when_not_initialized_with_type() + { + using (var stream = new MemoryStream()) + { + var writer = new StreamWriter(stream); + writer.WriteLine("23.4"); + writer.Flush(); + stream.Position = 0; + var msgArray = SerializerFactory.Create() + .Deserialize(stream, new[] + { + typeof(MessageWithDouble), + typeof(EmptyMessage) + }); + + Assert.AreEqual(23.4, ((MessageWithDouble)msgArray[0]).Double); + Assert.AreEqual(typeof(EmptyMessage), msgArray[1].GetType()); } } @@ -352,12 +499,14 @@ public void TestMultipleInterfacesDuplicatedProperty() [Test] public void Generic_properties_should_be_supported() { - var result = ExecuteSerializer.ForMessage(m => - { - m.GenericProperty = - new GenericProperty("test") { WhatEver = "a property" }; - }); + { + m.GenericProperty = + new GenericProperty("test") + { + WhatEver = "a property" + }; + }); Assert.AreEqual("a property", result.GenericProperty.WhatEver); } @@ -368,7 +517,10 @@ public void Culture() { var serializer = SerializerFactory.Create(); var val = 65.36; - var msg = new MessageWithDouble { Double = val }; + var msg = new MessageWithDouble + { + Double = val + }; Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); @@ -408,7 +560,12 @@ public void TestInterfaces() o.Int = 7; o.Name = "udi"; o.Uri = new Uri("http://www.UdiDahan.com/"); - o.Risk = new Risk { Percent = 0.15D, Annum = true, Accuracy = 0.314M }; + o.Risk = new Risk + { + Percent = 0.15D, + Annum = true, + Accuracy = 0.314M + }; o.Some = SomeEnum.B; o.Start = DateTime.Now; o.Duration = TimeSpan.Parse("-01:15:27.123"); @@ -416,14 +573,84 @@ public void TestInterfaces() o.Lookup = new MyDictionary(); o.Lookup["1"] = "1"; o.Foos = new Dictionary>(); - o.Foos["foo1"] = new List(new[] { new Foo { Name = "1", Title = "1" }, new Foo { Name = "2", Title = "2" } }); - o.Data = new byte[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 }; - o.SomeStrings = new List { "a", "b", "c" }; + o.Foos["foo1"] = new List(new[] + { + new Foo + { + Name = "1", + Title = "1" + }, + new Foo + { + Name = "2", + Title = "2" + } + }); + o.Data = new byte[] + { + 1, + 2, + 3, + 4, + 5, + 4, + 3, + 2, + 1 + }; + o.SomeStrings = new List + { + "a", + "b", + "c" + }; - o.ArrayFoos = new[] { new Foo { Name = "FooArray1", Title = "Mr." }, new Foo { Name = "FooAray2", Title = "Mrs" } }; - o.Bars = new[] { new Bar { Name = "Bar1", Length = 1 }, new Bar { Name = "BAr2", Length = 5 } }; - o.NaturalNumbers = new HashSet(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - o.Developers = new HashSet(new[] { "Udi Dahan", "Andreas Ohlund", "Matt Burton", "Jonathan Oliver et al" }); + o.ArrayFoos = new[] + { + new Foo + { + Name = "FooArray1", + Title = "Mr." + }, + new Foo + { + Name = "FooAray2", + Title = "Mrs" + } + }; + o.Bars = new[] + { + new Bar + { + Name = "Bar1", + Length = 1 + }, + new Bar + { + Name = "BAr2", + Length = 5 + } + }; + o.NaturalNumbers = new HashSet(new[] + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + }); + o.Developers = new HashSet(new[] + { + "Udi Dahan", + "Andreas Ohlund", + "Matt Burton", + "Jonathan Oliver et al" + }); o.Parent = mapper.CreateInstance(); o.Parent.Name = "udi"; @@ -431,7 +658,12 @@ public void TestInterfaces() o.Parent.Address = Guid.NewGuid().ToString(); o.Parent.Int = 7; o.Parent.Name = "-1"; - o.Parent.Risk = new Risk { Percent = 0.15D, Annum = true, Accuracy = 0.314M }; + o.Parent.Risk = new Risk + { + Percent = 0.15D, + Annum = true, + Accuracy = 0.314M + }; o.Names = new List(); for (var i = 0; i < number; i++) @@ -442,7 +674,12 @@ public void TestInterfaces() m1.Address = Guid.NewGuid().ToString(); m1.Int = 7; m1.Name = i.ToString(); - m1.Risk = new Risk { Percent = 0.15D, Annum = true, Accuracy = 0.314M }; + m1.Risk = new Risk + { + Percent = 0.15D, + Annum = true, + Accuracy = 0.314M + }; } o.MoreNames = o.Names.ToArray(); @@ -454,9 +691,19 @@ public void TestInterfaces() public void TestDataContractSerializer() { var o = CreateM2(); - var messages = new IMessage[] { o }; + var messages = new IMessage[] + { + o + }; - var dataContractSerializer = new DataContractSerializer(typeof(ArrayList), new[] { typeof(M2), typeof(SomeEnum), typeof(M1), typeof(Risk), typeof(List) }); + var dataContractSerializer = new DataContractSerializer(typeof(ArrayList), new[] + { + typeof(M2), + typeof(SomeEnum), + typeof(M1), + typeof(Risk), + typeof(List) + }); var sw = new Stopwatch(); sw.Start(); @@ -468,9 +715,9 @@ public void TestDataContractSerializer() var xmlReaderSettings = new XmlReaderSettings { - IgnoreProcessingInstructions = true, - ValidationType = ValidationType.None, - IgnoreWhitespace = true, + IgnoreProcessingInstructions = true, + ValidationType = ValidationType.None, + IgnoreWhitespace = true, CheckCharacters = false, ConformanceLevel = ConformanceLevel.Auto }; @@ -510,7 +757,13 @@ public void SerializeLists() var serializer = SerializerFactory.Create(); var msg = mapper.CreateInstance(); - msg.Items = new List { new MessageWithListItem { Data = "Hello" } }; + msg.Items = new List + { + new MessageWithListItem + { + Data = "Hello" + } + }; using (var stream = new MemoryStream()) { @@ -523,85 +776,109 @@ public void SerializeLists() } } - [Test] - public void SerializeClosedGenericListsInAlternateNamespace() - { - IMessageMapper mapper = new MessageMapper(); - var serializer = SerializerFactory.Create(); - var msg = mapper.CreateInstance(); + [Test] + public void SerializeClosedGenericListsInAlternateNamespace() + { + IMessageMapper mapper = new MessageMapper(); + var serializer = SerializerFactory.Create(); + var msg = mapper.CreateInstance(); - msg.Items = new AlternateNamespace.AlternateItemList { new AlternateNamespace.MessageWithListItemAlternate { Data = "Hello" } }; + msg.Items = new AlternateItemList + { + new MessageWithListItemAlternate + { + Data = "Hello" + } + }; - using (var stream = new MemoryStream()) - { - serializer.Serialize(msg, stream); - stream.Position = 0; + using (var stream = new MemoryStream()) + { + serializer.Serialize(msg, stream); + stream.Position = 0; - var msgArray = serializer.Deserialize(stream); - var m = (MessageWithClosedListInAlternateNamespace)msgArray[0]; - Assert.AreEqual("Hello", m.Items.First().Data); - } - } + var msgArray = serializer.Deserialize(stream); + var m = (MessageWithClosedListInAlternateNamespace)msgArray[0]; + Assert.AreEqual("Hello", m.Items.First().Data); + } + } [Test] - public void SerializeClosedGenericListsInAlternateNamespaceMultipleIEnumerableImplementations() - { - IMessageMapper mapper = new MessageMapper(); - var serializer = SerializerFactory.Create(); - var msg = mapper.CreateInstance(); + public void SerializeClosedGenericListsInAlternateNamespaceMultipleIEnumerableImplementations() + { + IMessageMapper mapper = new MessageMapper(); + var serializer = SerializerFactory.Create(); + var msg = mapper.CreateInstance(); - msg.Items = new AlternateNamespace.AlternateItemListMultipleIEnumerableImplementations { new AlternateNamespace.MessageWithListItemAlternate { Data = "Hello" } }; + msg.Items = new AlternateItemListMultipleIEnumerableImplementations + { + new MessageWithListItemAlternate + { + Data = "Hello" + } + }; - using (var stream = new MemoryStream()) - { - serializer.Serialize(msg, stream); - stream.Position = 0; + using (var stream = new MemoryStream()) + { + serializer.Serialize(msg, stream); + stream.Position = 0; - var msgArray = serializer.Deserialize(stream); - var m = (MessageWithClosedListInAlternateNamespaceMultipleIEnumerableImplementations)msgArray[0]; - Assert.AreEqual("Hello", m.Items.First().Data); - } - } + var msgArray = serializer.Deserialize(stream); + var m = (MessageWithClosedListInAlternateNamespaceMultipleIEnumerableImplementations)msgArray[0]; + Assert.AreEqual("Hello", m.Items.First().Data); + } + } [Test] - public void SerializeClosedGenericListsInAlternateNamespaceMultipleIListImplementations() - { - IMessageMapper mapper = new MessageMapper(); - var serializer = SerializerFactory.Create(); - var msg = mapper.CreateInstance(); - - msg.Items = new AlternateNamespace.AlternateItemListMultipleIListImplementations { new AlternateNamespace.MessageWithListItemAlternate { Data = "Hello" } }; - - using (var stream = new MemoryStream()) - { - serializer.Serialize(msg, stream); - stream.Position = 0; - - var msgArray = serializer.Deserialize(stream); - var m = (MessageWithClosedListInAlternateNamespaceMultipleIListImplementations)msgArray[0]; - Assert.AreEqual("Hello", m.Items.First().Data); - } - } - - [Test] - public void SerializeClosedGenericListsInSameNamespace() - { - IMessageMapper mapper = new MessageMapper(); - var serializer = SerializerFactory.Create(); - var msg = mapper.CreateInstance(); - - msg.Items = new ItemList { new MessageWithListItem { Data = "Hello" } }; - - using (var stream = new MemoryStream()) - { - serializer.Serialize(msg, stream); - stream.Position = 0; - - var msgArray = serializer.Deserialize(stream); - var m = (MessageWithClosedList)msgArray[0]; - Assert.AreEqual("Hello", m.Items.First().Data); - } - } + public void SerializeClosedGenericListsInAlternateNamespaceMultipleIListImplementations() + { + IMessageMapper mapper = new MessageMapper(); + var serializer = SerializerFactory.Create(); + var msg = mapper.CreateInstance(); + + msg.Items = new AlternateItemListMultipleIListImplementations + { + new MessageWithListItemAlternate + { + Data = "Hello" + } + }; + + using (var stream = new MemoryStream()) + { + serializer.Serialize(msg, stream); + stream.Position = 0; + + var msgArray = serializer.Deserialize(stream); + var m = (MessageWithClosedListInAlternateNamespaceMultipleIListImplementations)msgArray[0]; + Assert.AreEqual("Hello", m.Items.First().Data); + } + } + + [Test] + public void SerializeClosedGenericListsInSameNamespace() + { + IMessageMapper mapper = new MessageMapper(); + var serializer = SerializerFactory.Create(); + var msg = mapper.CreateInstance(); + + msg.Items = new ItemList + { + new MessageWithListItem + { + Data = "Hello" + } + }; + + using (var stream = new MemoryStream()) + { + serializer.Serialize(msg, stream); + stream.Position = 0; + + var msgArray = serializer.Deserialize(stream); + var m = (MessageWithClosedList)msgArray[0]; + Assert.AreEqual("Hello", m.Items.First().Data); + } + } [Test] public void SerializeEmptyLists() @@ -624,7 +901,7 @@ public void SerializeEmptyLists() } - private void DataContractSerialize(XmlWriterSettings xmlWriterSettings, DataContractSerializer dataContractSerializer, IMessage[] messages, Stream stream) + void DataContractSerialize(XmlWriterSettings xmlWriterSettings, DataContractSerializer dataContractSerializer, IMessage[] messages, Stream stream) { var o = new ArrayList(messages); using (var xmlWriter = XmlWriter.Create(stream, xmlWriterSettings)) @@ -657,7 +934,8 @@ M2 CreateM2() Parent = new M1 { Age = 10, - Address = Guid.NewGuid().ToString(), Int = 7, + Address = Guid.NewGuid().ToString(), + Int = 7, Name = "-1", Risk = new Risk { @@ -680,7 +958,7 @@ M2 CreateM2() m1.Risk = new Risk { Percent = 0.15D, - Annum = true, + Annum = true, Accuracy = 0.314M }; } @@ -690,7 +968,7 @@ M2 CreateM2() return o; } - private void Time(object message, IMessageSerializer serializer) + void Time(object message, IMessageSerializer serializer) { var watch = new Stopwatch(); watch.Start(); @@ -704,16 +982,22 @@ private void Time(object message, IMessageSerializer serializer) watch.Reset(); - var s = new MemoryStream(); - serializer.Serialize(message, s); - var buffer = s.GetBuffer(); - s.Dispose(); + byte[] buffer; + using (var s = new MemoryStream()) + { + serializer.Serialize(message, s); + buffer = s.ToArray(); + } watch.Start(); for (var i = 0; i < numberOfIterations; i++) + { using (var forDeserializing = new MemoryStream(buffer)) + { serializer.Deserialize(forDeserializing); + } + } watch.Stop(); Debug.WriteLine("Deserializing: " + watch.Elapsed); @@ -723,10 +1007,7 @@ private void Time(object message, IMessageSerializer serializer) [Test] public void NestedObjectWithNullPropertiesShouldBeSerialized() { - var result = ExecuteSerializer.ForMessage(m => - { - m.NestedObject = new MessageWithNullProperty(); - }); + var result = ExecuteSerializer.ForMessage(m => { m.NestedObject = new MessageWithNullProperty(); }); Assert.IsNotNull(result.NestedObject); } @@ -738,7 +1019,10 @@ public void Messages_with_generic_properties_closing_nullables_should_be_support var result = ExecuteSerializer.ForMessage( m => { - m.GenericNullable = new GenericPropertyWithNullable { TheType = theTime }; + m.GenericNullable = new GenericPropertyWithNullable + { + TheType = theTime + }; m.Whatever = "fdsfsdfsd"; }); Assert.IsNotNull(result.GenericNullable.TheType == theTime); @@ -757,40 +1041,35 @@ public void When_Using_A_Dictionary_With_An_Object_As_Value_should_throw() } - [Test, Ignore("We're not supporting this type")] public void System_classes_with_non_default_constructors_should_be_supported() { var message = new MailMessage("from@gmail.com", "to@hotmail.com") - { - Subject = "Testing the NSB email support", - Body = "Hello", - }; + { + Subject = "Testing the NSB email support", + Body = "Hello" + }; var result = ExecuteSerializer.ForMessage( - m => - { - m.MailMessage = message; - }); + m => { m.MailMessage = message; }); Assert.IsNotNull(result.MailMessage); Assert.AreEqual("from@gmail.com", result.MailMessage.From.Address); Assert.AreEqual(message.To.First(), result.MailMessage.To.First()); Assert.AreEqual(message.BodyEncoding.CodePage, result.MailMessage.BodyEncoding.CodePage); Assert.AreEqual(message.BodyEncoding.EncoderFallback.MaxCharCount, result.MailMessage.BodyEncoding.EncoderFallback.MaxCharCount); - } [Test, Ignore("We're currently not supporting polymorphic properties")] public void Messages_with_polymorphic_properties_should_be_supported() { var message = new PolyMessage - { - BaseType = new ChildOfBase - { - BaseTypeProp = "base", - ChildProp = "Child" - } - }; + { + BaseType = new ChildOfBase + { + BaseTypeProp = "base", + ChildProp = "Child" + } + }; var result = ExecuteSerializer.ForMessage(message); @@ -802,11 +1081,17 @@ public void Messages_with_polymorphic_properties_should_be_supported() [Test] public void When_Using_Property_WithXContainerAssignable_should_preserve_xml() { - const string XmlElement = ""; + const string XmlElement = ""; const string XmlDocument = "" + XmlElement; - var messageWithXDocument = new MessageWithXDocument { Document = XDocument.Load(new StringReader(XmlDocument)) }; - var messageWithXElement = new MessageWithXElement { Document = XElement.Load(new StringReader(XmlElement)) }; + var messageWithXDocument = new MessageWithXDocument + { + Document = XDocument.Load(new StringReader(XmlDocument)) + }; + var messageWithXElement = new MessageWithXElement + { + Document = XElement.Load(new StringReader(XmlElement)) + }; var resultXDocument = ExecuteSerializer.ForMessage(messageWithXDocument); var resultXElement = ExecuteSerializer.ForMessage(messageWithXElement); @@ -831,17 +1116,58 @@ public void Should_be_able_to_deserialize_many_messages_of_same_type() using (var stream = new MemoryStream()) { var streamWriter = new StreamWriter(stream); - streamWriter.Write(xml); + streamWriter.Write(xml); streamWriter.Flush(); stream.Position = 0; - var serializer = SerializerFactory.Create(); - var msgArray = serializer.Deserialize(stream, new[] { typeof(EmptyMessage) }); + var serializer = SerializerFactory.Create(); + var msgArray = serializer.Deserialize(stream, new[] + { + typeof(EmptyMessage) + }); Assert.AreEqual(3, msgArray.Length); } } + + [Test] + public void Object_property_with_primitive_or_struct_value_should_serialize_correctly() + { + // this fixes issue #2796 + + var serializer = SerializerFactory.Create(); + var message = new SerializedPair + { + Key = "AddressId", + Value = new Guid("{ebdeeb33-baa7-4100-b1aa-eb4d6816fd3d}") + }; + + object[] messageDeserialized; + using (var stream = new MemoryStream()) + { + serializer.Serialize(message, stream); + + stream.Position = 0; + + messageDeserialized = serializer.Deserialize(stream, new[] + { + message.GetType() + }); + } + + Assert.AreEqual(message.Key, ((SerializedPair)messageDeserialized[0]).Key); + Assert.AreEqual(message.Value, ((SerializedPair)messageDeserialized[0]).Value); + } + + int number = 1; + int numberOfIterations = 100; } - public class EmptyMessage:IMessage + public class SerializedPair + { + public string Key { get; set; } + public object Value { get; set; } + } + + public class EmptyMessage : IMessage { } @@ -862,6 +1188,7 @@ public class BaseType { public string BaseTypeProp { get; set; } } + public class MessageWithGenericPropClosingNullable { public GenericPropertyWithNullable GenericNullable { get; set; } @@ -882,7 +1209,6 @@ public class MessageWithGenericProperty { public GenericProperty GenericProperty { get; set; } public GenericProperty GenericPropertyThatIsNull { get; set; } - } public class MessageWithNestedObject @@ -904,20 +1230,12 @@ public class GenericPropertyWithNullable public class GenericProperty { - private T value; - public GenericProperty(T value) { - this.value = value; + ReadOnlyBlob = value; } - public T ReadOnlyBlob - { - get - { - return value; - } - } + public T ReadOnlyBlob { get; private set; } public string WhatEver { get; set; } } @@ -959,25 +1277,25 @@ public class MessageWithArrayList : IMessage public ArrayList ArrayList { get; set; } } - public class MessageWithClosedListInAlternateNamespace : IMessage - { - public AlternateNamespace.AlternateItemList Items { get; set; } - } + public class MessageWithClosedListInAlternateNamespace : IMessage + { + public AlternateItemList Items { get; set; } + } - public class MessageWithClosedListInAlternateNamespaceMultipleIEnumerableImplementations : IMessage - { - public AlternateNamespace.AlternateItemListMultipleIEnumerableImplementations Items { get; set; } - } + public class MessageWithClosedListInAlternateNamespaceMultipleIEnumerableImplementations : IMessage + { + public AlternateItemListMultipleIEnumerableImplementations Items { get; set; } + } - public class MessageWithClosedListInAlternateNamespaceMultipleIListImplementations : IMessage - { - public AlternateNamespace.AlternateItemListMultipleIListImplementations Items { get; set; } - } + public class MessageWithClosedListInAlternateNamespaceMultipleIListImplementations : IMessage + { + public AlternateItemListMultipleIListImplementations Items { get; set; } + } - public class MessageWithClosedList : IMessage - { - public ItemList Items { get; set; } - } + public class MessageWithClosedList : IMessage + { + public ItemList Items { get; set; } + } [Serializable] public class MessageWithXDocument : IMessage @@ -991,9 +1309,9 @@ public class MessageWithXElement : IMessage public XElement Document { get; set; } } - public class ItemList : List - { - } + public class ItemList : List + { + } public class CompositeMessage : IMyEventA, IMyEventB { @@ -1026,72 +1344,135 @@ namespace NServiceBus.Serializers.XML.Test.AlternateNamespace { using System.Collections.Generic; using System.Linq; + using System.Runtime.Serialization; public class AlternateItemList : List - { - } - - public class MessageWithListItemAlternate - { - public string Data { get; set; } - } - - public class AlternateItemListMultipleIEnumerableImplementations : List, IEnumerable - { - public new IEnumerator GetEnumerator() - { - return ToArray().Select(item => item.Data).GetEnumerator(); - } - } - - public class AlternateItemListMultipleIListImplementations : List, IList - { - private IList stringList = new List(); - - IEnumerator IEnumerable.GetEnumerator() - { - return stringList.GetEnumerator(); - } - - void ICollection.Add(string item) - { - stringList.Add(item); - } - - bool ICollection.Contains(string item) - { - return stringList.Contains(item); - } - - void ICollection.CopyTo(string[] array, int arrayIndex) - { - stringList.CopyTo(array, arrayIndex); - } - - bool ICollection.Remove(string item) - { - return stringList.Remove(item); - } - - bool ICollection.IsReadOnly - { - get { return stringList.IsReadOnly; } - } - - int IList.IndexOf(string item) - { - return stringList.IndexOf(item); - } - - void IList.Insert(int index, string item) - { - stringList.Insert(index, item); - } - - string IList.this[int index] - { - get { return stringList[index]; } - set { stringList[index] = value; } - } - } + { + } + + public class MessageWithListItemAlternate + { + public string Data { get; set; } + } + + public class AlternateItemListMultipleIEnumerableImplementations : List, IEnumerable + { + public new IEnumerator GetEnumerator() + { + return ToArray().Select(item => item.Data).GetEnumerator(); + } + } + + public class AlternateItemListMultipleIListImplementations : List, IList + { + IEnumerator IEnumerable.GetEnumerator() + { + return stringList.GetEnumerator(); + } + + void ICollection.Add(string item) + { + stringList.Add(item); + } + + bool ICollection.Contains(string item) + { + return stringList.Contains(item); + } + + void ICollection.CopyTo(string[] array, int arrayIndex) + { + stringList.CopyTo(array, arrayIndex); + } + + bool ICollection.Remove(string item) + { + return stringList.Remove(item); + } + + bool ICollection.IsReadOnly => stringList.IsReadOnly; + + int IList.IndexOf(string item) + { + return stringList.IndexOf(item); + } + + void IList.Insert(int index, string item) + { + stringList.Insert(index, item); + } + + string IList.this[int index] + { + get { return stringList[index]; } + set { stringList[index] = value; } + } + + IList stringList = new List(); + } + + class MessageImplementingISerializable : ISerializable + { + public MessageImplementingISerializable(string readOnlyProperty) + { + ReadOnlyProperty = readOnlyProperty; + } + + protected MessageImplementingISerializable(SerializationInfo info, StreamingContext context) + { + ReadOnlyProperty = info.GetString("ReadOnlyProperty"); + } + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("ReadOnlyProperty", ReadOnlyProperty); + } + + public string ReadOnlyProperty { get; } + } + + class MessageWithInvalidInterfaceProperty + { + public IInvalidInterfaceProperty InterfaceProperty { get; set; } + } + + public interface IInvalidInterfaceProperty + { + string SomeProperty { get; set; } + + void SomeMethod(); + } + + class InvalidInterfacePropertyImplementation : IInvalidInterfaceProperty + { + public string SomeProperty { get; set; } + + public void SomeMethod() + { + } + } + + class MessageWithInterfaceProperty + { + public IInterfaceProperty InterfaceProperty { get; set; } + } + + public interface IInterfaceProperty + { + string SomeProperty { get; set; } + } + + class InterfacePropertyImplementation : IInterfaceProperty + { + public string SomeProperty { get; set; } + } + + public interface IMessageWithInterfaceProperty + { + IInterfaceProperty InterfaceProperty { get; set; } + } + + class InterfaceMessageWithInterfacePropertyImplementation : IMessageWithInterfaceProperty + { + public IInterfaceProperty InterfaceProperty { get; set; } + } } diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/SerializingArrayTests.cs b/src/NServiceBus.Core.Tests/Serializers/XML/SerializingArrayTests.cs index b1c1e8c799e..350b520bf6a 100644 --- a/src/NServiceBus.Core.Tests/Serializers/XML/SerializingArrayTests.cs +++ b/src/NServiceBus.Core.Tests/Serializers/XML/SerializingArrayTests.cs @@ -2,7 +2,9 @@ namespace NServiceBus.Serializers.XML.Test { using System; using System.IO; + using System.Linq; using System.Text; + using System.Xml.Linq; using NUnit.Framework; [Serializable] @@ -118,17 +120,19 @@ public void CanSerializeNullableArrayWithNullString() SerializerFactory.Create().Serialize(message, stream); stream.Position = 0; var reader = new StreamReader(stream); - var xml = reader.ReadToEnd().Replace("\r\n", "\n").Replace("\n", "\r\n"); + var xml = reader.ReadToEnd(); - Assert.AreEqual(@" + var expected = XDocument.Parse(@" 00000000-0000-0000-0000-000000000000 - -null +null -", xml); +"); + var actual = XDocument.Parse(xml); + + Assert.AreEqual(expected.ToString(), actual.ToString()); } } @@ -154,6 +158,94 @@ public void CanDeserializeNullableArrayWithValueSetToNullString() } } + [Test] + public void CanDeserializeNullableArrayWithFirstEntryXsiNilAttributeSetToTrue() + { + var xml = @" + +00000000-0000-0000-0000-000000000000 + + + + +"; + var data = Encoding.UTF8.GetBytes(xml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullableArray) }); + var result = (MessageWithNullableArray)msgArray[0]; + + Assert.AreEqual(null, result.SomeInts[0]); + } + } + + [Test] + public void CanDeserializeNullableArrayWithXsiNilAttributeSetToTrue() + { + var xml = @" + +00000000-0000-0000-0000-000000000000 + + + +"; + var data = Encoding.UTF8.GetBytes(xml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullableArray) }); + var result = (MessageWithNullableArray)msgArray[0]; + + Assert.IsFalse(result.SomeInts.Any()); + } + } + + [Test] + public void CanDeserializeNullableArrayWithNoElementsToEmptyList() + { + var xml = @" + +00000000-0000-0000-0000-000000000000 + + + +"; + var data = Encoding.UTF8.GetBytes(xml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullableArray) }); + var result = (MessageWithNullableArray)msgArray[0]; + + Assert.NotNull(result.SomeInts); + Assert.AreEqual(0, result.SomeInts.Length); + } + } + + [Test] + public void CanDeserializeNullableArrayWithValueSetToEmptyString() + { + var xml = @" + +00000000-0000-0000-0000-000000000000 + + + + + +"; + var data = Encoding.UTF8.GetBytes(xml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullableArray) }); + var result = (MessageWithNullableArray)msgArray[0]; + + Assert.AreEqual(null, result.SomeInts[0]); + } + } + [Test] public void CanSerializeMessageWithNullableArray() { diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/SerializingNullableTypesTests.cs b/src/NServiceBus.Core.Tests/Serializers/XML/SerializingNullableTypesTests.cs new file mode 100644 index 00000000000..1091cb69c99 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Serializers/XML/SerializingNullableTypesTests.cs @@ -0,0 +1,134 @@ +namespace NServiceBus.Serializers.XML.Test +{ + using System; + using System.IO; + using System.Text; + using System.Xml.Linq; + using NUnit.Framework; + + [Serializable] + public class MessageWithNullable : IMessage + { + public string FirstName { get; set; } + + public string LastName { get; set; } + + public string EmailAddress { get; set; } + + public DateTime? BirthDate { get; set; } //Nullable DateTime property + } + + [TestFixture] + public class SerializingNullableTypesTests + { + [Test] + public void NullableTypesSerializeToXsiNilWhenNull() + { + var message = new MessageWithNullable + { + FirstName = "FirstName", + LastName = "LastName", + EmailAddress = "EmailAddress", + BirthDate = null + }; + + using (var stream = new MemoryStream()) + { + SerializerFactory.Create().Serialize(message, stream); + stream.Position = 0; + var reader = new StreamReader(stream); + var xml = reader.ReadToEnd(); + + var expected = XDocument.Parse(@" + + FirstName + LastName + EmailAddress + + +"); + var actual = XDocument.Parse(xml); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + } + } + + [Test] + public void NullableTypeSerializeToValueWhenNotNull() + { + var message = new MessageWithNullable + { + FirstName = "FirstName", + LastName = "LastName", + EmailAddress = "EmailAddress", + BirthDate = new DateTime(1950, 04, 25) + }; + + using (var stream = new MemoryStream()) + { + SerializerFactory.Create().Serialize(message, stream); + stream.Position = 0; + var reader = new StreamReader(stream); + var xml = reader.ReadToEnd(); + + var expected = XDocument.Parse(@" + + FirstName + LastName + EmailAddress + 1950-04-25T00:00:00 + +"); + var actual = XDocument.Parse(xml); + + Assert.AreEqual(expected.ToString(), actual.ToString()); + } + } + + [Test] + public void CanDeserializeNilMessage() + { + var messageXml = @" + + FirstName + LastName + EmailAddress + + +"; + + var data = Encoding.UTF8.GetBytes(messageXml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullable) }); + var result = (MessageWithNullable)msgArray[0]; + + Assert.AreEqual(null, result.BirthDate); + } + } + + [Test] + public void CanDeserializeOriginalNullValueMessage() + { + var messageXml = @" + + FirstName + LastName + EmailAddress + null + +"; + + var data = Encoding.UTF8.GetBytes(messageXml); + + using (var stream = new MemoryStream(data)) + { + var msgArray = SerializerFactory.Create().Deserialize(stream, new[] { typeof(MessageWithNullable) }); + var result = (MessageWithNullable)msgArray[0]; + + Assert.AreEqual(null, result.BirthDate); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Serializers/XML/Using_Infer_Type_With_Nested_Class.cs b/src/NServiceBus.Core.Tests/Serializers/XML/Using_Infer_Type_With_Nested_Class.cs deleted file mode 100644 index c69f372deb2..00000000000 --- a/src/NServiceBus.Core.Tests/Serializers/XML/Using_Infer_Type_With_Nested_Class.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus.Core.Tests.Serializers.XML -{ - using System.IO; - using NServiceBus.Serializers.XML.Test; - using NUnit.Framework; - - [TestFixture] - public class Using_Inferred_Nested_Types - { - [Test] - [Explicit] - public void Should_throw_an_exception() - { - var m2_1 = new M2(); - var m2_2 = new M2(); - var m2_3 = new M2(); - - using (Stream stream = new MemoryStream()) - { - var serializer = SerializerFactory.Create(typeof(IMyBusMessage), typeof(M1), typeof(M2)); - //var exception = Assert.Throws(() => serializer.Initialize(new[] { typeof(IMyBusMessage), typeof(M1), typeof(M2) })); - //Assert.AreEqual("Nested types are not supported by the XmlMessageSerializer.", exception.Message); - var m1 = new M1(); - serializer.Serialize(new object[] {m1, m2_1, m2_2, m2_3}, stream); - stream.Position = 0; - var messageDeserialized = serializer.Deserialize(stream); - Assert.IsInstanceOf(messageDeserialized[0]); - Assert.IsInstanceOf(messageDeserialized[1]); - Assert.IsInstanceOf(messageDeserialized[2]); - Assert.IsInstanceOf(messageDeserialized[3]); - } - - } - - public interface IMyBusMessage : IMessage - { - } - - public class M1 : IMyBusMessage - { - } - - public class M2 : IMyBusMessage - { - } - } -} diff --git a/src/NServiceBus.Core.Tests/Settings/SettingsHolderTests.cs b/src/NServiceBus.Core.Tests/Settings/SettingsHolderTests.cs new file mode 100644 index 00000000000..fc04fe6f4ac --- /dev/null +++ b/src/NServiceBus.Core.Tests/Settings/SettingsHolderTests.cs @@ -0,0 +1,79 @@ +namespace NServiceBus.Settings +{ + using System; + using System.Configuration; + using System.Linq; + using NUnit.Framework; + + [TestFixture] + public class SettingsHolderTests + { + [Test] + public void Clear_ShouldDisposeAllDisposables() + { + var firstOverrideDisposable = new SomeDisposable(); + var secondOverrideDisposable = new SomeDisposable(); + var firstDefaultDisposable = new SomeDisposable(); + var secondDefaultDisposable = new SomeDisposable(); + + var all = new[] + { + firstDefaultDisposable, + secondDefaultDisposable, + firstOverrideDisposable, + secondOverrideDisposable + }; + + var settings = new SettingsHolder(); + settings.Set("1.Override", firstOverrideDisposable); + settings.Set("2.Override", secondOverrideDisposable); + settings.SetDefault("1.Default", firstDefaultDisposable); + settings.SetDefault("2.Default", secondDefaultDisposable); + + settings.Clear(); + + Assert.IsTrue(all.All(x => x.Disposed)); + } + + [Test] + public void Merge_ShouldMergeContentFromSource() + { + var settings = new SettingsHolder(); + settings.SetDefault("SomeDefaultSetting", "Value1"); + settings.Set("SomeSetting", "Value1"); + + var mergeFrom = new SettingsHolder(); + mergeFrom.SetDefault("SomeDefaultSettingThatGetsMerged", "Value1"); + mergeFrom.Set("SomeSettingThatGetsMerged", "Value1"); + + settings.Merge(mergeFrom); + + var result1 = settings.Get("SomeDefaultSettingThatGetsMerged"); + var result2 = settings.Get("SomeSettingThatGetsMerged"); + + Assert.AreEqual("Value1", result1); + Assert.AreEqual("Value1", result2); + } + + [Test] + public void Merge_ThrowsWhenChangesArePrevented() + { + var settings = new SettingsHolder(); + var mergeFrom = new SettingsHolder(); + + settings.PreventChanges(); + + Assert.Throws(() => settings.Merge(mergeFrom)); + } + + class SomeDisposable : IDisposable + { + public void Dispose() + { + Disposed = true; + } + + public bool Disposed; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/StandardsTests.cs b/src/NServiceBus.Core.Tests/StandardsTests.cs index 1b1c1c0d2dc..fc3a06ce261 100644 --- a/src/NServiceBus.Core.Tests/StandardsTests.cs +++ b/src/NServiceBus.Core.Tests/StandardsTests.cs @@ -4,10 +4,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; + using System.Runtime.CompilerServices; using NServiceBus.Features; using NServiceBus.Logging; + using NServiceBus.Pipeline; using NUnit.Framework; - using UnicastBus = NServiceBus.Unicast.UnicastBus; [TestFixture] public class StandardsTests @@ -29,12 +30,45 @@ public void VerifyFeatureNaming() } } + + [Test] + public void NonPublicShouldHaveSimpleNamespace() + { + // we still need an NServiceBus prefix for people who do logging filtering + var types = typeof(Endpoint).Assembly.GetTypes() + .Where(x => + !x.IsPublic && + !x.IsNested && + !IsCompilerGenerated(x) && + !x.FullName.Contains("JetBrains") && + !x.FullName.StartsWith("Newtonsoft.Json") && + !x.FullName.StartsWith("Autofac") && + x.Name != "GitVersionInformation" && + x.Namespace != "Particular.Licensing" && + x.Namespace != "NServiceBus.Features" && + x.Name != "ProcessedByFody" && + x.Namespace != "NServiceBus").ToList(); + if (types.Count > 0) + { + Assert.IsEmpty(types, "Non public types should have 'NServiceBus' namespace\r\n" + string.Join(Environment.NewLine, types.Select(x => x.FullName))); + } + } + + static bool IsCompilerGenerated(Type x) + { + return Attribute.IsDefined(x, typeof(CompilerGeneratedAttribute), false); + } [Test] public void LoggersShouldBeStaticField() { - foreach (var type in typeof(UnicastBus).Assembly.GetTypes()) + var exceptions = new[] + { + typeof(RoutingFeature) //field of type ILog generated by the compiler. + }; + //Exclude types in the exceptions collection and their nested types + foreach (var type in typeof(Endpoint).Assembly.GetTypes().Except(exceptions).Where(t => !exceptions.Contains(t.DeclaringType))) { - foreach (var field in type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public)) + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { if (field.FieldType == typeof(ILog)) { @@ -51,7 +85,7 @@ public void VerifyBehaviorNaming() { Assert.IsFalse(featureType.IsPublic, "Behaviors should internal " + featureType.FullName); Assert.AreEqual("NServiceBus", featureType.Namespace, "Behaviors should be in the NServiceBus namespace since it reduces the 'wall of text' problem when looking at pipeline stack traces. " + featureType.FullName); - Assert.IsTrue(featureType.Name.EndsWith("Behavior"), "Behaviors should be suffixed with 'Behavior'. " + featureType.FullName); + Assert.IsTrue(featureType.Name.EndsWith("Terminator") || featureType.Name.EndsWith("Behavior") || featureType.Name.EndsWith("Connector"), "Behaviors should be suffixed with 'Behavior' or 'Connector'. " + featureType.FullName); } } @@ -66,24 +100,26 @@ public void VerifyAttributesAreSealed() static IEnumerable GetBehaviors() { - return typeof(UnicastBus).Assembly.GetTypes() - .Where(type => type.GetInterfaces().Any(face=>face.Name.StartsWith("IBehavior")) && !type.IsAbstract); + return typeof(Endpoint).Assembly.GetTypes() + .Where(type => type.GetInterfaces().Any(face => face.Name == typeof(IBehavior).Name) && !type.IsAbstract && !type.IsGenericType); } static IEnumerable GetFeatures() { - return typeof(UnicastBus).Assembly.GetTypes() - .Where(type => typeof(Feature).IsAssignableFrom(type) && !type.IsAbstract); + return typeof(Endpoint).Assembly.GetTypes() + .Where(type => typeof(Feature).IsAssignableFrom(type) && type.IsPublic && !type.IsAbstract); } static IEnumerable GetAttributeTypes() { - return typeof(UnicastBus).Assembly.GetTypes() + return typeof(Endpoint).Assembly.GetTypes() .Where(type => type.Namespace != null && typeof(Attribute).IsAssignableFrom(type) && //Ignore log4net attributes !type.Namespace.Contains("log4net") && //Ignore Newtonsoft attributes !type.Namespace.Contains("Newtonsoft") && + //Ignore Resharper annotations + !type.Namespace.Contains("JetBrains") && //TODO: remove when gitversion is updated !type.Name.EndsWith("ReleaseDateAttribute") && !type.Name.EndsWith("NugetVersionAttribute")); diff --git a/src/NServiceBus.Core.Tests/StructConventionsTests.ApproveStructsWhichDontFollowStructGuidelines.approved.txt b/src/NServiceBus.Core.Tests/StructConventionsTests.ApproveStructsWhichDontFollowStructGuidelines.approved.txt new file mode 100644 index 00000000000..55d0562650c --- /dev/null +++ b/src/NServiceBus.Core.Tests/StructConventionsTests.ApproveStructsWhichDontFollowStructGuidelines.approved.txt @@ -0,0 +1,37 @@ +-------------------------------------------------- REMEMBER -------------------------------------------------- +CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects. + +AVOID defining a struct unless the type has all of the following characteristics: + * It logically represents a single value, similar to primitive types(int, double, etc.). + * It has an instance size under 16 bytes. + * It is immutable. + * It will not have to be boxed frequently. + +In all other cases, you should define your types as classes. +-------------------------------------------------- REMEMBER -------------------------------------------------- + +NServiceBus.LogicalAddress violates the following rules: + - The following fields are reference types, which are potentially mutable: + - Field k__BackingField of type NServiceBus.Routing.EndpointInstance is a reference type. + +NServiceBus.MessageQueueExtensions+ACE_HEADER violates the following rules: + - The following fields are public, so the type is not immutable: + - Field AceType of type System.Byte is public. + - Field AceFlags of type System.Byte is public. + - Field AceSize of type System.Int16 is public. + +NServiceBus.MessageQueueExtensions+ACCESS_ALLOWED_ACE violates the following rules: + - The following fields are public, so the type is not immutable: + - Field Header of type NServiceBus.MessageQueueExtensions+ACE_HEADER is public. + - Field Mask of type System.UInt32 is public. + - Field SidStart of type System.Int32 is public. + +NServiceBus.MessageQueueExtensions+ACL_SIZE_INFORMATION violates the following rules: + - The following fields are public, so the type is not immutable: + - Field AceCount of type System.UInt32 is public. + - Field AclBytesInUse of type System.UInt32 is public. + - Field AclBytesFree of type System.UInt32 is public. + +NServiceBus.Features.SLAMonitoring+EstimatedTimeToSLABreachCounter+DataPoint violates the following rules: + - The size is 24 bytes, which exceeds the recommended maximum of 16 bytes. + diff --git a/src/NServiceBus.Core.Tests/StructConventionsTests.cs b/src/NServiceBus.Core.Tests/StructConventionsTests.cs new file mode 100644 index 00000000000..62c85ba9eb6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/StructConventionsTests.cs @@ -0,0 +1,139 @@ +namespace NServiceBus.Core.Tests +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.InteropServices; + using System.Text; + using ApprovalTests; + using NUnit.Framework; + + [TestFixture] + public class StructConventionsTests + { + [Test] + public void ApproveStructsWhichDontFollowStructGuidelines() + { + var approvalBuilder = new StringBuilder(); + approvalBuilder.AppendLine(@"-------------------------------------------------- REMEMBER -------------------------------------------------- +CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects. + +AVOID defining a struct unless the type has all of the following characteristics: + * It logically represents a single value, similar to primitive types(int, double, etc.). + * It has an instance size under 16 bytes. + * It is immutable. + * It will not have to be boxed frequently. + +In all other cases, you should define your types as classes. +-------------------------------------------------- REMEMBER -------------------------------------------------- +"); + + var assembly = typeof(Endpoint).Assembly; + foreach (var type in assembly.GetTypes()) + { + if (!type.IsValueType || type.IsEnum || type.IsSpecialName|| type.Namespace == null || !type.Namespace.StartsWith("NServiceBus") || type.FullName.Contains("__")) continue; + + var violatedRules = new List { $"{type.FullName} violates the following rules:" }; + + InspectSizeOfStruct(type, violatedRules); + InspectWhetherStructViolatesMutabilityRules(type, violatedRules); + + if (violatedRules.Count <= 1) continue; + foreach (var violatedRule in violatedRules) + { + approvalBuilder.AppendLine(violatedRule); + } + approvalBuilder.AppendLine(); + } + + Approvals.Verify(approvalBuilder.ToString()); + } + + static void InspectWhetherStructViolatesMutabilityRules(Type type, List violatedRules) + { + InspectWhetherStructContainsReferenceTypes(type, violatedRules); + InspectWhetherStructContainsPublicFields(type, violatedRules); + InspectWhetherStructContainsWritableProperties(type, violatedRules); + } + + static void InspectWhetherStructContainsReferenceTypes(Type type, List violatedRules) + { + var mutabilityRules = new List { " - The following fields are reference types, which are potentially mutable:" }; + + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + foreach (var fieldInfo in fields) + { + if (fieldInfo.FieldType == typeof(string) && (fieldInfo.IsInitOnly || fieldInfo.IsLiteral)) + { + continue; + } + + if (fieldInfo.FieldType.IsClass || fieldInfo.FieldType.IsInterface) + { + mutabilityRules.Add($" - Field {fieldInfo.Name} of type { fieldInfo.FieldType } is a reference type."); + } + } + + if(mutabilityRules.Count > 1) + violatedRules.AddRange(mutabilityRules); + } + + static void InspectWhetherStructContainsPublicFields(Type type, List violatedRules) + { + var mutabilityRules = new List { " - The following fields are public, so the type is not immutable:" }; + + var fields = type.GetFields(); + + foreach (var fieldInfo in fields) + { + if (!fieldInfo.IsInitOnly && !fieldInfo.IsLiteral) + { + mutabilityRules.Add($" - Field {fieldInfo.Name} of type { fieldInfo.FieldType } is public."); + } + } + + if (mutabilityRules.Count > 1) + violatedRules.AddRange(mutabilityRules); + } + + static void InspectWhetherStructContainsWritableProperties(Type type, List violatedRules) + { + var mutabilityRules = new List { " - The following properties can be written to, so the type is not immutable:" }; + + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + foreach (var property in properties) + { + if (property.CanWrite) + { + mutabilityRules.Add($" - Property {property.Name} of type { property.PropertyType } can be written to."); + } + } + + if (mutabilityRules.Count > 1) + violatedRules.AddRange(mutabilityRules); + } + + static void InspectSizeOfStruct(Type type, List violatedRules) + { + try + { + var sizeOf = Marshal.SizeOf(type); + if (IsLargerThanSixteenBytes(sizeOf)) + { + violatedRules.Add($" - The size is {sizeOf} bytes, which exceeds the recommended maximum of 16 bytes."); + } + } + catch (Exception) + { + violatedRules.Add(" - The size cannot be determined. This type likely violates all struct rules."); + } + } + + static bool IsLargerThanSixteenBytes(int sizeOf) + { + return sizeOf > 16; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithNoDirectReference.dll b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithNoDirectReference.dll new file mode 100644 index 00000000000..c18765d45ad Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithNoDirectReference.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithRefToSN.dll b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithRefToSN.dll new file mode 100644 index 00000000000..ca9d8981932 Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithRefToSN.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithReference.dll b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithReference.dll new file mode 100644 index 00000000000..44ef1b5698d Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithReference.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithSN.dll b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithSN.dll new file mode 100644 index 00000000000..d90235c55a2 Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/AssemblyWithSN.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.NewCompilerBits.dll b/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.NewCompilerBits.dll deleted file mode 100644 index 849bb494804..00000000000 Binary files a/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.NewCompilerBits.dll and /dev/null differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.OldCompilerBits.dll b/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.OldCompilerBits.dll deleted file mode 100644 index b4e8e6b6bdf..00000000000 Binary files a/src/NServiceBus.Core.Tests/TestDlls/NServiceBus.OldCompilerBits.dll and /dev/null differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/Nested/nested.dll b/src/NServiceBus.Core.Tests/TestDlls/Nested/nested.dll new file mode 100644 index 00000000000..30967a892a8 Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/Nested/nested.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/RoslynX64.dll b/src/NServiceBus.Core.Tests/TestDlls/RoslynX64.dll new file mode 100644 index 00000000000..f606f0360a4 Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/RoslynX64.dll differ diff --git a/src/NServiceBus.Core.Tests/TestDlls/RoslynX86.dll b/src/NServiceBus.Core.Tests/TestDlls/RoslynX86.dll new file mode 100644 index 00000000000..e3b99e3a525 Binary files /dev/null and b/src/NServiceBus.Core.Tests/TestDlls/RoslynX86.dll differ diff --git a/src/NServiceBus.Core.Tests/Timeout/FakeMessageDispatcher.cs b/src/NServiceBus.Core.Tests/Timeout/FakeMessageDispatcher.cs new file mode 100644 index 00000000000..0253eca218b --- /dev/null +++ b/src/NServiceBus.Core.Tests/Timeout/FakeMessageDispatcher.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Core.Tests.Timeout +{ + using System.Threading.Tasks; + using Extensibility; + using Transport; + + public class FakeMessageDispatcher : IDispatchMessages + { + public int MessagesSent + { + get { return messagesSent; } + set { messagesSent = value; } + } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transportTransaction, ContextBag context) + { + MessagesSent += outgoingMessages.MulticastTransportOperations.Count + outgoingMessages.UnicastTransportOperations.Count; + return TaskEx.CompletedTask; + } + + volatile int messagesSent; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/FakeMessageSender.cs b/src/NServiceBus.Core.Tests/Timeout/FakeMessageSender.cs deleted file mode 100644 index bc95f8a70b9..00000000000 --- a/src/NServiceBus.Core.Tests/Timeout/FakeMessageSender.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NServiceBus.Core.Tests.Timeout -{ - using Transports; - using Unicast; - - public class FakeMessageSender : ISendMessages - { - private volatile int messagesSent; - - public int MessagesSent - { - get { return messagesSent; } - set { messagesSent = value; } - } - - public void Send(TransportMessage message, SendOptions sendOptions) - { - MessagesSent++; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterTests.cs b/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterTests.cs index baada09b173..a0e178dad4e 100644 --- a/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterTests.cs +++ b/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterTests.cs @@ -2,7 +2,8 @@ namespace NServiceBus.Core.Tests.Timeout { using System; using System.Linq; - using NServiceBus.InMemory.TimeoutPersister; + using System.Threading.Tasks; + using Extensibility; using NServiceBus.Timeout.Core; using NUnit.Framework; @@ -10,103 +11,142 @@ namespace NServiceBus.Core.Tests.Timeout public class InMemoryTimeoutPersisterTests { [Test] - public void When_empty_NextTimeToRunQuery_is_1_minute() + public async Task When_empty_NextTimeToRunQuery_is_1_minute() { - DateTime nextTimeToRunQuery; var now = DateTime.UtcNow; - new InMemoryTimeoutPersister().GetNextChunk(now, out nextTimeToRunQuery); - Assert.That(nextTimeToRunQuery, Is.EqualTo(now.AddMinutes(1)).Within(100).Milliseconds); + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var result = await persister.GetNextChunk(now); + Assert.That(result.NextTimeToQuery, Is.EqualTo(now.AddMinutes(1)).Within(100).Milliseconds); } [Test] - public void When_multiple_NextTimeToRunQuery_is_min_date() + public async Task When_multiple_NextTimeToRunQuery_is_min_date() { - DateTime nextTimeToRunQuery; var now = DateTime.UtcNow; - var persister = new InMemoryTimeoutPersister(); - persister.Add(new TimeoutData + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(2) - }); + }, new ContextBag()); var expectedDate = DateTime.Now.AddDays(1); - persister.Add(new TimeoutData + await persister.Add(new TimeoutData { Time = expectedDate - }); - persister.GetNextChunk(now, out nextTimeToRunQuery); - Assert.AreEqual(expectedDate, nextTimeToRunQuery); + }, new ContextBag()); + + var result = await persister.GetNextChunk(now); + + Assert.AreEqual(expectedDate, result.NextTimeToQuery); } [Test] - public void When_multiple_future_are_returned() + public async Task When_multiple_future_are_returned() { - DateTime nextTime; - var persister = new InMemoryTimeoutPersister(); - persister.Add(new TimeoutData + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-2) - }); - persister.Add(new TimeoutData + }, new ContextBag()); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-4) - }); - persister.Add(new TimeoutData + }, new ContextBag()); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-1) - }); - var nextChunk = persister.GetNextChunk(DateTime.Now.AddDays(-3), out nextTime); - Assert.AreEqual(2, nextChunk.Count()); + }, new ContextBag()); + + var result = await persister.GetNextChunk(DateTime.Now.AddDays(-3)); + + Assert.AreEqual(2, result.DueTimeouts.Count()); + } + + [Test] + public async Task TryRemove_when_existing_is_removed_should_return_true() + { + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var inputTimeout = new TimeoutData(); + await persister.Add(inputTimeout, new ContextBag()); + + var result = await persister.TryRemove(inputTimeout.Id, new ContextBag()); + + Assert.IsTrue(result); + } + + [Test] + public async Task TryRemove_when_non_existing_is_removed_should_return_false() + { + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var inputTimeout = new TimeoutData(); + await persister.Add(inputTimeout, new ContextBag()); + + var result = await persister.TryRemove(Guid.NewGuid().ToString(), new ContextBag()); + + Assert.False(result); } [Test] - public void When_existing_is_removed_existing_is_outted() + public async Task Peek_when_timeout_exists_should_return_timeout() { - var persister = new InMemoryTimeoutPersister(); + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); var inputTimeout = new TimeoutData(); - persister.Add(inputTimeout); - TimeoutData removedTimeout; - var removed = persister.TryRemove(inputTimeout.Id, out removedTimeout); - Assert.IsTrue(removed); - Assert.AreSame(inputTimeout, removedTimeout); + await persister.Add(inputTimeout, new ContextBag()); + + var result = await persister.Peek(inputTimeout.Id, new ContextBag()); + + Assert.AreSame(inputTimeout, result); } [Test] - public void When_existing_is_removed_by_saga_id() + public async Task Peek_when_timeout_does_not_exist_should_return_null() { - var persister = new InMemoryTimeoutPersister(); + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + var inputTimeout = new TimeoutData(); + await persister.Add(inputTimeout, new ContextBag()); + + var result = await persister.Peek(Guid.NewGuid().ToString(), new ContextBag()); + + Assert.IsNull(result); + } + + [Test] + public async Task When_existing_is_removed_by_saga_id() + { + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); var newGuid = Guid.NewGuid(); var inputTimeout = new TimeoutData { SagaId = newGuid }; - persister.Add(inputTimeout); - persister.RemoveTimeoutBy(newGuid); - TimeoutData removedTimeout; - var removed = persister.TryRemove(inputTimeout.Id, out removedTimeout); - Assert.False(removed); + await persister.Add(inputTimeout, new ContextBag()); + await persister.RemoveTimeoutBy(newGuid, new ContextBag()); + var result = await persister.TryRemove(inputTimeout.Id, new ContextBag()); + + Assert.IsFalse(result); } [Test] - public void When_all_in_past_NextTimeToRunQuery_is_1_minute() + public async Task When_all_in_past_NextTimeToRunQuery_is_1_minute() { - DateTime nextTimeToRunQuery; var now = DateTime.UtcNow; - var persister = new InMemoryTimeoutPersister(); - persister.Add(new TimeoutData + var persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-1) - }); - persister.Add(new TimeoutData + }, new ContextBag()); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-3) - }); - persister.Add(new TimeoutData + }, new ContextBag()); + await persister.Add(new TimeoutData { Time = DateTime.Now.AddDays(-2) - }); - persister.GetNextChunk(now, out nextTimeToRunQuery); - Assert.That(nextTimeToRunQuery, Is.EqualTo(now.AddMinutes(1)).Within(100).Milliseconds); + }, new ContextBag()); + + var result = await persister.GetNextChunk(now); + + Assert.That(result.NextTimeToQuery, Is.EqualTo(now.AddMinutes(1)).Within(100).Milliseconds); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterThreadTests.cs b/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterThreadTests.cs deleted file mode 100644 index aefe0660614..00000000000 --- a/src/NServiceBus.Core.Tests/Timeout/InMemoryTimeoutPersisterThreadTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace NServiceBus.Core.Tests.Timeout -{ - using System; - using System.Collections.Concurrent; - using System.Diagnostics; - using System.Linq; - using System.Threading; - using NServiceBus.InMemory.TimeoutPersister; - using NServiceBus.Timeout.Core; - using NUnit.Framework; - - [TestFixture] - public class InMemoryTimeoutPersisterThreadTests - { - - ConcurrentDictionary sagaIdGuids = new ConcurrentDictionary(); - - [Test] - [Explicit] - public void Run() - { - var stopwatch = Stopwatch.StartNew(); - var inMemoryTimeoutPersister = new InMemoryTimeoutPersister(); - - for (var i = 0; i < 10; i++) - { - var thread = new Thread(() => Runner(inMemoryTimeoutPersister)); - thread.Start(); - thread.Join(); - } - Debug.WriteLine(stopwatch.ElapsedMilliseconds); - } - - void Runner(InMemoryTimeoutPersister inMemoryTimeoutPersister) - { - for (var i = 0; i < 10000; i++) - { - GetNextChunk(inMemoryTimeoutPersister); - Add(inMemoryTimeoutPersister); - GetNextChunk(inMemoryTimeoutPersister); - TryRemove(inMemoryTimeoutPersister); - GetNextChunk(inMemoryTimeoutPersister); - RemoveTimeoutBy(inMemoryTimeoutPersister); - GetNextChunk(inMemoryTimeoutPersister); - } - } - - void RemoveTimeoutBy(InMemoryTimeoutPersister inMemoryTimeoutPersister) - { - var sagaId = sagaIdGuids.GetOrAdd(Thread.CurrentThread.ManagedThreadId, new Guid()); - inMemoryTimeoutPersister.RemoveTimeoutBy(sagaId); - } - - void TryRemove(InMemoryTimeoutPersister inMemoryTimeoutPersister) - { - TimeoutData timeout; - inMemoryTimeoutPersister.TryRemove(Thread.CurrentThread.Name, out timeout); - } - - void Add(InMemoryTimeoutPersister inMemoryTimeoutPersister) - { - inMemoryTimeoutPersister.Add(new TimeoutData - { - Time = DateTime.Now, - Id = Thread.CurrentThread.Name - }); - } - - void GetNextChunk(InMemoryTimeoutPersister inMemoryTimeoutPersister) - { - for (var i = 0; i < 10; i++) - { - DateTime nextTimeToRunQuery; - inMemoryTimeoutPersister.GetNextChunk(DateTime.MinValue, out nextTimeToRunQuery).ToList(); - } - } - } - -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/When_fetching_timeouts_from_storage.cs b/src/NServiceBus.Core.Tests/Timeout/When_fetching_timeouts_from_storage.cs index 6d5889be792..eebdc47c2b8 100644 --- a/src/NServiceBus.Core.Tests/Timeout/When_fetching_timeouts_from_storage.cs +++ b/src/NServiceBus.Core.Tests/Timeout/When_fetching_timeouts_from_storage.cs @@ -1,61 +1,53 @@ namespace NServiceBus.Core.Tests.Timeout { using System; - using System.Collections.Generic; using System.Linq; - using InMemory.TimeoutPersister; + using System.Threading.Tasks; + using Extensibility; using NServiceBus.Timeout.Core; using NUnit.Framework; [TestFixture] - public class When_fetching_timeouts_from_storage_with_inMemory : When_fetching_timeouts_from_storage + public class When_fetching_timeouts_from_storage_with_inMemory { - protected override IPersistTimeouts CreateTimeoutPersister() - { - return new InMemoryTimeoutPersister(); - } - } - - public abstract class When_fetching_timeouts_from_storage - { - protected IPersistTimeouts persister; - - protected abstract IPersistTimeouts CreateTimeoutPersister(); - + InMemoryTimeoutPersister persister; + [SetUp] public void Setup() { - persister = CreateTimeoutPersister(); + persister = new InMemoryTimeoutPersister(() => DateTime.UtcNow); } [Test] - public void Should_only_return_timeouts_for_time_slice() + public async Task Should_only_return_timeouts_for_time_slice() { const int numberOfTimeoutsToAdd = 10; for (var i = 0; i < numberOfTimeoutsToAdd; i++) { - persister.Add(new TimeoutData + await persister.Add(new TimeoutData { - OwningTimeoutManager = String.Empty, + OwningTimeoutManager = string.Empty, Time = DateTime.UtcNow.AddHours(-1) - }); + }, new ContextBag()); } for (var i = 0; i < numberOfTimeoutsToAdd; i++) { - persister.Add(new TimeoutData + await persister.Add(new TimeoutData { - OwningTimeoutManager = String.Empty, + OwningTimeoutManager = string.Empty, Time = DateTime.UtcNow.AddHours(1) - }); + }, new ContextBag()); } - - Assert.AreEqual(numberOfTimeoutsToAdd, GetNextChunk().Count()); + + var nextChunk = await GetNextChunk(); + + Assert.AreEqual(numberOfTimeoutsToAdd, nextChunk.DueTimeouts.Count()); } [Test] - public void Should_set_the_next_run() + public async Task Should_set_the_next_run() { const int numberOfTimeoutsToAdd = 50; @@ -67,28 +59,26 @@ public void Should_set_the_next_run() OwningTimeoutManager = "MyEndpoint" }; - persister.Add(d); + await persister.Add(d, new ContextBag()); } var expected = DateTime.UtcNow.AddHours(1); - persister.Add(new TimeoutData + await persister.Add(new TimeoutData { Time = expected, - OwningTimeoutManager = String.Empty, - }); + OwningTimeoutManager = string.Empty, + }, new ContextBag()); - DateTime nextTimeToRunQuery; - persister.GetNextChunk(DateTime.UtcNow.AddYears(-3), out nextTimeToRunQuery); + var nextChunk = await GetNextChunk(); - var totalMilliseconds = (expected - nextTimeToRunQuery).Duration().TotalMilliseconds; + var totalMilliseconds = (expected - nextChunk.NextTimeToQuery).Duration().TotalMilliseconds; Assert.True(totalMilliseconds < 200); } - protected IEnumerable> GetNextChunk() + Task GetNextChunk() { - DateTime nextTimeToRunQuery; - return persister.GetNextChunk(DateTime.UtcNow.AddYears(-3), out nextTimeToRunQuery); + return persister.GetNextChunk(DateTime.UtcNow.AddYears(-3)); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/When_pooling_timeouts.cs b/src/NServiceBus.Core.Tests/Timeout/When_pooling_timeouts.cs deleted file mode 100644 index 38d2ba06e9a..00000000000 --- a/src/NServiceBus.Core.Tests/Timeout/When_pooling_timeouts.cs +++ /dev/null @@ -1,187 +0,0 @@ -namespace NServiceBus.Core.Tests.Timeout -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using InMemory.TimeoutPersister; - using NServiceBus.Timeout.Core; - using NServiceBus.Timeout.Hosting.Windows; - using NUnit.Framework; - - [TestFixture] - [Explicit] - class When_pooling_timeouts_with_inMemory : When_pooling_timeouts - { - protected override IPersistTimeouts CreateTimeoutPersister() - { - return new InMemoryTimeoutPersister(); - } - } - - abstract class When_pooling_timeouts - { - DefaultTimeoutManager manager; - FakeMessageSender messageSender; - readonly Random rand = new Random(); - int expected; - - IPersistTimeouts persister; - TimeoutPersisterReceiver receiver; - - protected abstract IPersistTimeouts CreateTimeoutPersister(); - - [SetUp] - public void Setup() - { - persister = CreateTimeoutPersister(); - messageSender = new FakeMessageSender(); - - manager = new DefaultTimeoutManager - { - TimeoutsPersister = persister, - MessageSender = messageSender, - }; - - receiver = new TimeoutPersisterReceiver - { - TimeoutManager = manager, - TimeoutsPersister = persister, - MessageSender = messageSender, - SecondsToSleepBetweenPolls = 1, - }; - } - - [Test] - public void Should_retrieve_all_timeout_messages_that_expired() - { - expected = 50; - - for (var i = 0; i < expected; i++) - persister.Add(CreateData(DateTime.UtcNow.AddSeconds(-5))); - - StartAndStopReceiver(); - - WaitForMessagesThenAssert(5); - } - - [Test] - public void Should_pickup_future_timeout_messages_and_send_when_expired() - { - expected = 1; - - manager.PushTimeout(CreateData(DateTime.UtcNow.AddSeconds(2))); - - StartAndStopReceiver(5); - - WaitForMessagesThenAssert(5); - } - - [Test] - public void Should_send_more_timeout_messages_after_completed_a_batch() - { - receiver.Start(); - - expected = 10; - Push(expected, DateTime.UtcNow.AddSeconds(1)); - - Thread.Sleep(TimeSpan.FromSeconds(5)); - - WaitForMessagesThenAssert(10); - - messageSender.MessagesSent = 0; - expected = 30; - - Push(expected, DateTime.UtcNow.AddSeconds(3)); - - Thread.Sleep(TimeSpan.FromSeconds(8)); - receiver.Stop(); - - WaitForMessagesThenAssert(10); - } - - [Test] - public void Should_pickup_new_timeout_messages_as_they_arrive_and_send_all() - { - expected = 100; - - receiver.Start(); - - Task.Factory.StartNew(() => - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - Push(50, DateTime.UtcNow.AddSeconds(rand.Next(0, 5))); - }); - - Push(50, DateTime.UtcNow.AddSeconds(1)); - - Thread.Sleep(TimeSpan.FromSeconds(20)); - - receiver.Stop(); - - WaitForMessagesThenAssert(10); - } - - [Test] - public void Should_pickup_new_timeout_messages_even_if_they_due_now_and_send_all() - { - expected = 25; - - receiver.Start(); - - Push(25, DateTime.UtcNow.AddSeconds(1)); - - Thread.Sleep(TimeSpan.FromSeconds(5)); - - WaitForMessagesThenAssert(5); - - messageSender.MessagesSent = 0; - expected = 40; - - Task.Factory.StartNew(() => Push(10, DateTime.UtcNow.AddSeconds(rand.Next(0, 30)))); - - Push(30, DateTime.UtcNow.AddSeconds(3)); - - Thread.Sleep(TimeSpan.FromSeconds(40)); - - receiver.Stop(); - - WaitForMessagesThenAssert(10); - } - - private void Push(int total, DateTime time) - { - for (var i = 0; i < total; i++) - manager.PushTimeout(CreateData(time)); - } - - private void StartAndStopReceiver(int secondsToWaitBeforeCallingStop = 1) - { - receiver.Start(); - Thread.Sleep(TimeSpan.FromSeconds(secondsToWaitBeforeCallingStop)); - receiver.Stop(); - } - - private static TimeoutData CreateData(DateTime time) - { - return new TimeoutData - { - OwningTimeoutManager = "MyEndpoint", - Time = time, - Headers = new Dictionary(), - }; - } - - private void WaitForMessagesThenAssert(int maxSecondsToWait) - { - var maxTime = DateTime.Now.AddSeconds(maxSecondsToWait); - - while (messageSender.MessagesSent < expected && DateTime.Now < maxTime) - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - } - - Assert.AreEqual(expected, messageSender.MessagesSent); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/When_receiving_timeouts.cs b/src/NServiceBus.Core.Tests/Timeout/When_receiving_timeouts.cs deleted file mode 100644 index 9bbcba2121d..00000000000 --- a/src/NServiceBus.Core.Tests/Timeout/When_receiving_timeouts.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.Core.Tests.Timeout -{ - using System; - using NServiceBus.Timeout.Core; - using NUnit.Framework; - - [TestFixture] - public class When_receiving_timeouts - { - - [Test] - public void Should_dispatch_timeout_if_is_due_now() - { - var messageSender = new FakeMessageSender(); - - var configure = new BusConfiguration().BuildConfiguration(); - - configure.localAddress = new Address("sdad", "asda"); - var manager = new DefaultTimeoutManager - { - MessageSender = messageSender, - Configure = configure - }; - - manager.PushTimeout(new TimeoutData - { - Time = DateTime.UtcNow, - }); - - Assert.AreEqual(1, messageSender.MessagesSent); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Timeout/When_removing_timeouts_from_the_storage.cs b/src/NServiceBus.Core.Tests/Timeout/When_removing_timeouts_from_the_storage.cs deleted file mode 100644 index dcd425c215f..00000000000 --- a/src/NServiceBus.Core.Tests/Timeout/When_removing_timeouts_from_the_storage.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace NServiceBus.Core.Tests.Timeout -{ - using System; - using System.Collections.Generic; - using System.Linq; - using InMemory.TimeoutPersister; - using NServiceBus.Timeout.Core; - using NUnit.Framework; - - [TestFixture] - public class When_removing_timeouts_from_the_storage_with_inMemory : When_removing_timeouts_from_the_storage - { - protected override IPersistTimeouts CreateTimeoutPersister() - { - return new InMemoryTimeoutPersister(); - } - } - - public abstract class When_removing_timeouts_from_the_storage - { - protected IPersistTimeouts persister; - - protected abstract IPersistTimeouts CreateTimeoutPersister(); - - [SetUp] - public void Setup() - { - persister = CreateTimeoutPersister(); - } - - [Test] - public void Should_remove_timeouts_by_id() - { - var t1 = new TimeoutData {Id = "1", Time = DateTime.UtcNow.AddHours(-1)}; - persister.Add(t1); - - var t2 = new TimeoutData {Id = "2", Time = DateTime.UtcNow.AddHours(-1)}; - persister.Add(t2); - - var timeouts = GetNextChunk(); - - foreach (var timeout in timeouts) - { - TimeoutData timeoutData; - persister.TryRemove(timeout.Item1, out timeoutData); - } - - Assert.AreEqual(0, GetNextChunk().Count()); - } - - protected IEnumerable> GetNextChunk() - { - DateTime nextTimeToRunQuery; - return persister.GetNextChunk(DateTime.UtcNow.AddYears(-3), out nextTimeToRunQuery); - } - } -} diff --git a/src/NServiceBus.Core.Tests/Transport/When_specifying_a_non_zero_throughput_limit.cs b/src/NServiceBus.Core.Tests/Transport/When_specifying_a_non_zero_throughput_limit.cs deleted file mode 100644 index ebb335f48c4..00000000000 --- a/src/NServiceBus.Core.Tests/Transport/When_specifying_a_non_zero_throughput_limit.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus.Core.Tests.Transport -{ - using System.Threading; - using NUnit.Framework; - - [TestFixture,Explicit("Timing sensitive")] - public class When_specifying_a_non_zero_throughput_limit : for_the_transactional_transport - { - const int ThroughputLimit = 4; - - [Test] - public void Should_limit_the_throughput_to_the_set_limit() - { - TransportReceiver.ChangeMaximumMessageThroughputPerSecond(ThroughputLimit); - TransportReceiver.Start(Address.Parse("mytest")); - - ThreadPool.QueueUserWorkItem(Receive10); - - Thread.Sleep(600); - Assert.AreEqual(ThroughputLimit, fakeReceiver.NumberOfMessagesReceived); - - Thread.Sleep(500); - Assert.AreEqual(ThroughputLimit * 2, fakeReceiver.NumberOfMessagesReceived); - } - - private void Receive10(object state) - { - for (var i = 0; i < ThroughputLimit*2; i++) - { - fakeReceiver.FakeMessageReceived(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transport/When_specifying_a_zero_throughput_limit.cs b/src/NServiceBus.Core.Tests/Transport/When_specifying_a_zero_throughput_limit.cs deleted file mode 100644 index 2e2555346f1..00000000000 --- a/src/NServiceBus.Core.Tests/Transport/When_specifying_a_zero_throughput_limit.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NServiceBus.Core.Tests.Transport -{ - using NUnit.Framework; - - [TestFixture] - public class When_specifying_a_zero_throughput_limit : for_the_transactional_transport - { - [Test] - public void Should_not_limit_the_throughput() - { - const int throughputLimit = 0; - - TransportReceiver.ChangeMaximumMessageThroughputPerSecond(throughputLimit); - TransportReceiver.Start(Address.Parse("myTest")); - - for (var i = 0; i < 100; i++) - { - fakeReceiver.FakeMessageReceived(); - - } - Assert.AreEqual(100, fakeReceiver.NumberOfMessagesReceived); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transport/for_the_transactional_transport.cs b/src/NServiceBus.Core.Tests/Transport/for_the_transactional_transport.cs deleted file mode 100644 index f2d063bd51b..00000000000 --- a/src/NServiceBus.Core.Tests/Transport/for_the_transactional_transport.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NServiceBus.Core.Tests.Transport -{ - using System; - using System.Transactions; - using Fakes; - using NServiceBus.Faults; - using NUnit.Framework; - using Settings; - using Unicast.Transport; - using TransactionSettings = Unicast.Transport.TransactionSettings; - - public class for_the_transactional_transport - { - [SetUp] - public void SetUp() - { - fakeReceiver = new FakeReceiver(); - - TransportReceiver = new TransportReceiver(new TransactionSettings(true, TimeSpan.FromSeconds(30), IsolationLevel.ReadCommitted, 5, false,false), 1, 0, fakeReceiver, new FakeFailureManager(), new SettingsHolder(), new BusConfiguration().BuildConfiguration()); - - } - - protected FakeReceiver fakeReceiver; - protected TransportReceiver TransportReceiver; - - class FakeFailureManager : IManageMessageFailures - { - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - - } - - public void Init(Address address) - { - - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileFeatureTests.cs b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileFeatureTests.cs new file mode 100644 index 00000000000..a5fee008a4c --- /dev/null +++ b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileFeatureTests.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Core.Tests.Routing.FileBasedDynamicRouting +{ + using System; + using System.Linq; + using NServiceBus.Features; + using NUnit.Framework; + using Settings; + + [TestFixture] + public class InstanceMappingFileFeatureTests + { + [Test] + public void Should_configure_default_values() + { + var feature = new InstanceMappingFileFeature(); + var settings = new SettingsHolder(); + + feature.ConfigureDefaults(settings); + + Assert.That(settings.Get(InstanceMappingFileFeature.FilePathSettingsKey), Is.EqualTo("instance-mapping.xml")); + Assert.That(settings.Get(InstanceMappingFileFeature.CheckIntervalSettingsKey), Is.EqualTo(TimeSpan.FromSeconds(30))); + } + + [Test] + public void Requires_routing_enabled() + { + var feature = new InstanceMappingFileFeature(); + + Assert.That(feature.Dependencies.Any(dependencies => dependencies.Any(dependency => dependency == typeof(RoutingFeature).FullName))); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileMonitorTests.cs b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileMonitorTests.cs new file mode 100644 index 00000000000..e58cc99c435 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileMonitorTests.cs @@ -0,0 +1,172 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using System.Xml.Linq; + using NServiceBus.Logging; + using NServiceBus.Routing; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class InstanceMappingFileMonitorTests + { + StringBuilder logOutput; + + [OneTimeSetUp] + public void TestFixtureSetup() + { + var loggerFactory = LogManager.Use(); + loggerFactory.Level(LogLevel.Info); + logOutput = new StringBuilder(); + var stringWriter = new StringWriter(logOutput); + loggerFactory.WriteTo(stringWriter); + } + + [SetUp] + public void Setup() + { + logOutput.Clear(); + } + + [Test] + public void Reload_should_throw_when_file_does_not_exist() + { + const string filePath = "some file path"; + var timer = new FakeTimer(); + var fileAccessException = new Exception("Simulated"); + var fileAccess = new FakeFileAccess(() => + { + throw fileAccessException; + }); + var monitor = new InstanceMappingFileMonitor(filePath, TimeSpan.Zero, timer, fileAccess, new EndpointInstances()); + + var exception = Assert.Throws(() => monitor.ReloadData()); + + Assert.That(exception.Message, Does.Contain($"An error occurred while reading the endpoint instance mapping file at {filePath}. See the inner exception for more details.")); + Assert.That(exception.InnerException, Is.EqualTo(fileAccessException)); + } + + [Test] + public async Task It_logs_error_when_file_access_fails_during_runtime() + { + var errorCallbackInvoked = false; + var timer = new FakeTimer(ex => + { + errorCallbackInvoked = true; + }); + var fail = false; + var fileAccess = new FakeFileAccess(() => + { + // ReSharper disable once AccessToModifiedClosure + if (fail) + { + throw new Exception("Simulated"); + } + return XDocument.Parse(@""); + }); + + var monitor = new InstanceMappingFileMonitor("unused", TimeSpan.Zero, timer, fileAccess, new EndpointInstances()); + await monitor.PerformStartup(null); + + fail = true; + await timer.Trigger(); + + Assert.IsTrue(errorCallbackInvoked); + } + + [Test] + public void Should_log_added_endpoints() + { + var fileAccess = new FakeFileAccess(() => XDocument.Parse(@"")); + var monitor = new InstanceMappingFileMonitor("filepath", TimeSpan.Zero, new FakeTimer(), fileAccess, new EndpointInstances()); + + monitor.ReloadData(); + + Assert.That(logOutput.ToString(), Does.Contain(@"Updating instance mapping table from 'filepath': +Added endpoint 'A' with 2 instances")); + } + + [Test] + public void Should_log_removed_endpoints() + { + var fileData = new Queue(); + fileData.Enqueue(@""); + fileData.Enqueue(@""); + var fileAccess = new FakeFileAccess(() => XDocument.Parse(fileData.Dequeue())); + var monitor = new InstanceMappingFileMonitor("filepath", TimeSpan.Zero, new FakeTimer(), fileAccess, new EndpointInstances()); + + monitor.ReloadData(); + logOutput.Clear(); + monitor.ReloadData(); + + Assert.That(logOutput.ToString(), Does.Contain(@"Updating instance mapping table from 'filepath': +Removed all instances of endpoint 'A'")); + } + + [Test] + public void Should_log_changed_instances() + { + var fileData = new Queue(); + fileData.Enqueue(@""); + fileData.Enqueue(@""); + var fileAccess = new FakeFileAccess(() => XDocument.Parse(fileData.Dequeue())); + var monitor = new InstanceMappingFileMonitor("filepath", TimeSpan.Zero, new FakeTimer(), fileAccess, new EndpointInstances()); + + monitor.ReloadData(); + logOutput.Clear(); + monitor.ReloadData(); + + Assert.That(logOutput.ToString(), Does.Contain(@"Updating instance mapping table from 'filepath': +Updated endpoint 'A': +2 instances, -1 instance")); + } + + class FakeFileAccess : IInstanceMappingFileAccess + { + readonly Func docCallback; + + public FakeFileAccess(Func docCallback) + { + this.docCallback = docCallback; + } + + public XDocument Load(string path) => docCallback(); + } + + class FakeTimer : IAsyncTimer + { + Func theCallback; + Action theErrorCallback; + Action errorSpyCallback; + + public FakeTimer(Action errorSpyCallback = null) + { + this.errorSpyCallback = errorSpyCallback; + } + + public async Task Trigger() + { + try + { + await theCallback().ConfigureAwait(false); + } + catch (Exception ex) + { + theErrorCallback(ex); + errorSpyCallback?.Invoke(ex); + } + } + + public void Start(Func callback, TimeSpan interval, Action errorCallback) + { + theCallback = callback; + theErrorCallback = errorCallback; + } + + public Task Stop() => TaskEx.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileParserTests.cs b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileParserTests.cs new file mode 100644 index 00000000000..8c4b1bfb16e --- /dev/null +++ b/src/NServiceBus.Core.Tests/Transports/MSMQ/InstanceMapping/InstanceMappingFileParserTests.cs @@ -0,0 +1,79 @@ +namespace NServiceBus.Core.Tests.Routing +{ + using System.Xml.Linq; + using System.Xml.Schema; + using NServiceBus.Routing; + using NUnit.Framework; + + [TestFixture] + public class InstanceMappingFileParserTests + { + [Test] + public void It_can_parse_valid_file() + { + const string xml = @" + + + + + + + + + +"; + var doc = XDocument.Parse(xml); + var result = new InstanceMappingFileParser().Parse(doc); + + CollectionAssert.AreEqual(new[] + { + new EndpointInstance("A", "D1").SetProperty("prop1", "V1").SetProperty("prop2","V2"), + new EndpointInstance("A").SetProperty("prop3", "V3").SetProperty("prop4", "V4"), + new EndpointInstance("B", "D2").SetProperty("prop5", "V5").SetProperty("prop6", "V6") + }, result); + } + + [Test] + public void It_allows_empty_endpoints_element() + { + const string xml = @" + + +"; + var doc = XDocument.Parse(xml); + var parser = new InstanceMappingFileParser(); + + Assert.DoesNotThrow(() => parser.Parse(doc)); + } + + [Test] + public void It_requires_endpoint_name() + { + const string xml = @" + + + +"; + var doc = XDocument.Parse(xml); + var parser = new InstanceMappingFileParser(); + + var exception = Assert.Throws(() => parser.Parse(doc)); + Assert.That(exception.Message, Does.Contain("The required attribute 'name' is missing.")); + } + + [Test] + public void It_requires_endpoint_to_have_an_instance() + { + const string xml = @" + + + +"; + var doc = XDocument.Parse(xml); + var parser = new InstanceMappingFileParser(); + + var exception = Assert.Throws(() => parser.Parse(doc)); + Assert.That(exception.Message, Does.Contain("The element 'endpoint' has incomplete content. List of possible elements expected: 'instance'.")); + } + } +} diff --git a/src/NServiceBus.Core.Tests/Transports/PushRuntimeSettingsTests.cs b/src/NServiceBus.Core.Tests/Transports/PushRuntimeSettingsTests.cs new file mode 100644 index 00000000000..deae2cf1461 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Transports/PushRuntimeSettingsTests.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.Core.Tests.Transports +{ + using System; + using NUnit.Framework; + using Transport; + + [TestFixture] + public class PushRuntimeSettingsTests + { + [Test] + public void Should_default_concurrency_to_num_processors() + { + Assert.AreEqual(Math.Max(2, Environment.ProcessorCount), new PushRuntimeSettings().MaxConcurrency); + } + + [Test] + public void Should_honor_explicit_concurrency_settings() + { + Assert.AreEqual(10, new PushRuntimeSettings(10).MaxConcurrency); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Transports/TransportOperationsTests.cs b/src/NServiceBus.Core.Tests/Transports/TransportOperationsTests.cs new file mode 100644 index 00000000000..a9fcbca9e7a --- /dev/null +++ b/src/NServiceBus.Core.Tests/Transports/TransportOperationsTests.cs @@ -0,0 +1,70 @@ +namespace NServiceBus.Core.Tests.Transports +{ + using System; + using System.Collections.Generic; + using System.Linq; + using NServiceBus.Routing; + using Transport; + using NUnit.Framework; + + [TestFixture] + public class TransportOperationsTests + { + [Test] + public void Should_split_multicast_and_unicast_messages() + { + var unicastOperation = new TransportOperation(CreateUniqueMessage(), new UnicastAddressTag("destination"), DispatchConsistency.Isolated); + var multicastOperation = new TransportOperation(CreateUniqueMessage(), new MulticastAddressTag(typeof(object)), DispatchConsistency.Default); + var operations = new[] + { + unicastOperation, + multicastOperation + }; + + var result = new TransportOperations(operations); + + Assert.AreEqual(1, result.MulticastTransportOperations.Count()); + var multicastOp = result.MulticastTransportOperations.Single(); + Assert.AreEqual(multicastOperation.Message, multicastOp.Message); + Assert.AreEqual((multicastOperation.AddressTag as MulticastAddressTag)?.MessageType, multicastOp.MessageType); + Assert.AreEqual(multicastOperation.DeliveryConstraints, multicastOp.DeliveryConstraints); + Assert.AreEqual(multicastOperation.RequiredDispatchConsistency, multicastOp.RequiredDispatchConsistency); + + Assert.AreEqual(1, result.UnicastTransportOperations.Count()); + var unicastOp = result.UnicastTransportOperations.Single(); + Assert.AreEqual(unicastOperation.Message, unicastOp.Message); + Assert.AreEqual((unicastOperation.AddressTag as UnicastAddressTag)?.Destination, unicastOp.Destination); + Assert.AreEqual(unicastOperation.DeliveryConstraints, unicastOp.DeliveryConstraints); + Assert.AreEqual(unicastOperation.RequiredDispatchConsistency, unicastOp.RequiredDispatchConsistency); + } + + [Test] + public void When_no_messages_should_return_empty_lists() + { + var result = new TransportOperations(); + + Assert.AreEqual(0, result.MulticastTransportOperations.Count()); + Assert.AreEqual(0, result.UnicastTransportOperations.Count()); + } + + [Test] + public void When_providing_unsupported_addressTag_should_throw() + { + var transportOperation = new TransportOperation( + CreateUniqueMessage(), + new CustomAddressTag(), + DispatchConsistency.Default); + + Assert.Throws(() => new TransportOperations(transportOperation)); + } + + static OutgoingMessage CreateUniqueMessage() + { + return new OutgoingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[0]); + } + + class CustomAddressTag : AddressTag + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Config/ConfigurationSettings.cs b/src/NServiceBus.Core.Tests/Unicast/Config/ConfigurationSettings.cs index 930053058cc..f4315a90823 100644 --- a/src/NServiceBus.Core.Tests/Unicast/Config/ConfigurationSettings.cs +++ b/src/NServiceBus.Core.Tests/Unicast/Config/ConfigurationSettings.cs @@ -1,6 +1,7 @@ namespace NServiceBus.Unicast.Config.Tests { - using NServiceBus.Features; + using System.Threading.Tasks; + using Features; using NUnit.Framework; [TestFixture] @@ -44,29 +45,31 @@ public void Generic_type_definition_handler_should_not_be_classified_as_a_handle } [Test] - public void Specific_generic_type_definition_handler_should_not_be_classified_as_a_handler() + public void Specific_generic_type_definition_handler_should_be_classified_as_a_handler() { Assert.IsTrue(RegisterHandlersInOrder.IsMessageHandler(typeof(GenericTypeDefinitionHandler))); } [Test] - public void Generic_implemented_type_definition_handler_should_not_be_classified_as_a_handler() + public void Generic_implemented_type_definition_handler_should_be_classified_as_a_handler() { Assert.IsTrue(RegisterHandlersInOrder.IsMessageHandler(typeof(GenericImplementedHandler))); } - + public class SimpleHandler : IHandleMessages { - public void Handle(SimpleMessage message) + public Task Handle(SimpleMessage message, IMessageHandlerContext context) { + return TaskEx.CompletedTask; } } public class GenericTypeDefinitionHandler : IHandleMessages { - public void Handle(SimpleMessage message) + public Task Handle(SimpleMessage message, IMessageHandlerContext context) { + return TaskEx.CompletedTask; } } @@ -84,8 +87,9 @@ public class ConcreteImplementationOfAbstractHandler : AbstractHandler public abstract class AbstractHandler : IHandleMessages { - public void Handle(SimpleMessage message) + public Task Handle(SimpleMessage message, IMessageHandlerContext context) { + return TaskEx.CompletedTask; } } diff --git a/src/NServiceBus.Core.Tests/Unicast/ConfiguringMessageEndpointMapping.cs b/src/NServiceBus.Core.Tests/Unicast/ConfiguringMessageEndpointMapping.cs index 18033bac234..21e37542fe2 100644 --- a/src/NServiceBus.Core.Tests/Unicast/ConfiguringMessageEndpointMapping.cs +++ b/src/NServiceBus.Core.Tests/Unicast/ConfiguringMessageEndpointMapping.cs @@ -22,9 +22,9 @@ namespace NServiceBus.Unicast.Tests public class Configuring_message_endpoint_mapping { - public IDictionary Configure(Action setupMapping) + public IDictionary Configure(Action setupMapping) { - var mappings = new Dictionary(); + var mappings = new Dictionary(); var mapping = new MessageEndpointMapping{ Endpoint = "SomeEndpoint" }; @@ -60,10 +60,9 @@ public void Should_map_all_the_types_in_the_assembly() public class When_configuring_an_endpoint_mapping_using_an_assembly_name_in_the_messages_property_that_does_not_exist : Configuring_message_endpoint_mapping { [Test] - [ExpectedException(typeof(ArgumentException))] public void Should_fail() { - Configure(m => m.Messages = "NServiceBus.Unicast.Tests.MessagesThatDoesNotExist"); + Assert.That(() => Configure(m => m.Messages = "NServiceBus.Unicast.Tests.MessagesThatDoesNotExist"), Throws.ArgumentException); } } @@ -81,10 +80,9 @@ public void Should_only_map_the_type() public class When_configuring_an_endpoint_mapping_using_a_type_name_in_the_messages_property_that_does_not_exist : Configuring_message_endpoint_mapping { [Test] - [ExpectedException(typeof(ArgumentException))] public void Should_fail() { - Configure(m => m.Messages = "NServiceBus.Unicast.Tests.Messages.MessageThatDoesNotExist, NServiceBus.Core.Tests"); + Assert.That(() => Configure(m => m.Messages = "NServiceBus.Unicast.Tests.Messages.MessageThatDoesNotExist, NServiceBus.Core.Tests"), Throws.ArgumentException); } } @@ -103,10 +101,9 @@ public void Should_map_all_the_types_in_the_assembly() public class When_configuring_an_endpoint_mapping_using_the_assembly_property_with_an_assembly_that_does_not_exist : Configuring_message_endpoint_mapping { [Test] - [ExpectedException(typeof(ArgumentException))] public void Should_fail() { - Configure(m => m.AssemblyName = "NServiceBus.Unicast.Tests.MessagesThatDoesNotExist"); + Assert.That(() => Configure(m => m.AssemblyName = "NServiceBus.Unicast.Tests.MessagesThatDoesNotExist"), Throws.ArgumentException); } } @@ -124,10 +121,9 @@ public void Should_only_map_the_type() public class When_configuring_an_endpoint_mapping_using_the_type_property_with_a_type_that_does_not_exist : Configuring_message_endpoint_mapping { [Test] - [ExpectedException(typeof(ArgumentException))] public void Should_fail() { - Configure(m => { m.AssemblyName = "NServiceBus.Core.Tests"; m.TypeFullName = "NServiceBus.Unicast.Tests.Messages.MessageThatDoesNotExist"; }); + Assert.That(() => Configure(m => { m.AssemblyName = "NServiceBus.Core.Tests"; m.TypeFullName = "NServiceBus.Unicast.Tests.Messages.MessageThatDoesNotExist"; }), Throws.ArgumentException); } } diff --git a/src/NServiceBus.Core.Tests/Unicast/Contexts/CommandWithDataBusPropertyMessage.cs b/src/NServiceBus.Core.Tests/Unicast/Contexts/CommandWithDataBusPropertyMessage.cs deleted file mode 100644 index ddbbf900d21..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Contexts/CommandWithDataBusPropertyMessage.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Contexts -{ - public class CommandWithDataBusPropertyMessage : ICommand - { - public DataBusProperty MyData { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Contexts/FakeMessageDeferrer.cs b/src/NServiceBus.Core.Tests/Unicast/Contexts/FakeMessageDeferrer.cs deleted file mode 100644 index b10c125a975..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Contexts/FakeMessageDeferrer.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Contexts -{ - using Transports; - - class FakeMessageDeferrer:IDeferMessages - { - public void Defer(TransportMessage message, SendOptions sendOptions) - { - - - } - - public void ClearDeferredMessages(string headerKey, string headerValue) - { - - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Contexts/using_the_unicastbus.cs b/src/NServiceBus.Core.Tests/Unicast/Contexts/using_the_unicastbus.cs deleted file mode 100644 index 14412e86444..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Contexts/using_the_unicastbus.cs +++ /dev/null @@ -1,325 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Contexts -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Runtime.Serialization; - using System.Threading; - using Core.Tests; - using Helpers; - using Licensing; - using MessageInterfaces; - using MessageInterfaces.MessageMapper.Reflection; - using MessageMutator; - using Monitoring; - using NServiceBus.Hosting; - using NServiceBus.ObjectBuilder; - using NUnit.Framework; - using Pipeline; - using Publishing; - using Rhino.Mocks; - using Routing; - using Serialization; - using Serializers.XML; - using Settings; - using Subscriptions.MessageDrivenSubscriptions; - using Timeout; - using Transports; - using Unicast.Messages; - using UnitOfWork; - - class using_a_configured_unicastBus - { - protected UnicastBus bus; - - protected ISendMessages messageSender; - protected FakeSubscriptionStorage subscriptionStorage; - - protected MessageMapper MessageMapper = new MessageMapper(); - - protected FakeTransport Transport; - protected XmlMessageSerializer MessageSerializer; - protected FuncBuilder FuncBuilder; - public static Address MasterNodeAddress; - protected EstimatedTimeToSLABreachCalculator SLABreachCalculator = (EstimatedTimeToSLABreachCalculator) FormatterServices.GetUninitializedObject(typeof(EstimatedTimeToSLABreachCalculator)); - protected MessageMetadataRegistry MessageMetadataRegistry; - protected SubscriptionManager subscriptionManager; - protected StaticMessageRouter router; - - protected MessageHandlerRegistry handlerRegistry; - protected TransportDefinition transportDefinition; - protected SettingsHolder settings; - protected Configure configure; - protected PipelineModifications pipelineModifications; - - PipelineExecutor pipelineFactory; - - static using_a_configured_unicastBus() - { - var localAddress = "endpointA"; - MasterNodeAddress = new Address(localAddress, "MasterNode"); - } - - [SetUp] - public void SetUp() - { - LicenseManager.InitializeLicense(); - transportDefinition = new MsmqTransport(); - - settings = new SettingsHolder(); - - settings.SetDefault("EndpointName", "TestEndpoint"); - settings.SetDefault("Endpoint.SendOnly", false); - settings.SetDefault("MasterNode.Address", MasterNodeAddress); - pipelineModifications = new PipelineModifications(); - settings.Set(pipelineModifications); - - ApplyPipelineModifications(); - - Transport = new FakeTransport(); - FuncBuilder = new FuncBuilder(); - - FuncBuilder.Register(() => settings); - - router = new StaticMessageRouter(KnownMessageTypes()); - var conventions = new Conventions(); - handlerRegistry = new MessageHandlerRegistry(conventions); - MessageMetadataRegistry = new MessageMetadataRegistry(false, conventions); - MessageSerializer = new XmlMessageSerializer(MessageMapper, conventions); - - messageSender = MockRepository.GenerateStub(); - subscriptionStorage = new FakeSubscriptionStorage(); - configure = new Configure(settings, FuncBuilder, new List>(), new PipelineSettings(null)) - { - localAddress = Address.Parse("TestEndpoint") - }; - - subscriptionManager = new SubscriptionManager - { - MessageSender = messageSender, - SubscriptionStorage = subscriptionStorage, - Configure = configure - }; - - pipelineFactory = new PipelineExecutor(settings, FuncBuilder, new BusNotifications()); - - FuncBuilder.Register(() => MessageSerializer); - FuncBuilder.Register(() => messageSender); - - FuncBuilder.Register(() => new LogicalMessageFactory(MessageMetadataRegistry, MessageMapper, pipelineFactory)); - - FuncBuilder.Register(() => subscriptionManager); - FuncBuilder.Register(() => SLABreachCalculator); - FuncBuilder.Register(() => MessageMetadataRegistry); - - FuncBuilder.Register(() => handlerRegistry); - FuncBuilder.Register(() => MessageMapper); - - FuncBuilder.Register(() => new DeserializeLogicalMessagesBehavior - { - MessageSerializer = MessageSerializer, - MessageMetadataRegistry = MessageMetadataRegistry, - }); - - FuncBuilder.Register(() => new CreatePhysicalMessageBehavior()); - FuncBuilder.Register(() => pipelineFactory); - FuncBuilder.Register(() => transportDefinition); - - var messagePublisher = new StorageDrivenPublisher - { - MessageSender = messageSender, - SubscriptionStorage = subscriptionStorage - }; - - var deferrer = new TimeoutManagerDeferrer - { - MessageSender = messageSender, - TimeoutManagerAddress = MasterNodeAddress.SubScope("Timeouts"), - Configure = configure, - }; - - FuncBuilder.Register(() => deferrer); - FuncBuilder.Register(() => messagePublisher); - - bus = new UnicastBus - { - Builder = FuncBuilder, - MessageSender = messageSender, - Transport = Transport, - MessageMapper = MessageMapper, - SubscriptionManager = subscriptionManager, - MessageRouter = router, - Settings = settings, - Configure = configure, - HostInformation = new HostInformation(Guid.NewGuid(), "HelloWorld") - }; - - FuncBuilder.Register(() => new CausationMutator { Bus = bus }); - FuncBuilder.Register(() => bus); - FuncBuilder.Register(() => bus); - FuncBuilder.Register(() => conventions); - FuncBuilder.Register(() => configure); - } - - protected virtual void ApplyPipelineModifications() - { - } - - protected virtual IEnumerable KnownMessageTypes() - { - return new Collection(); - } - - protected void VerifyThatMessageWasSentTo(Address destination) - { - messageSender.AssertWasCalled(x => x.Send(Arg.Is.Anything, Arg.Matches(o => o.Destination == destination))); - } - - protected void VerifyThatMessageWasSentWithHeaders(Func, bool> predicate) - { - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(t => predicate(t.Headers)), Arg.Is.Anything)); - } - - protected void RegisterUow(IManageUnitsOfWork uow) - { - FuncBuilder.Register(() => uow); - } - - protected void RegisterMessageHandlerType() where T : new() - { -// ReSharper disable HeapView.SlowDelegateCreation - FuncBuilder.Register(() => new T()); -// ReSharper restore HeapView.SlowDelegateCreation - - handlerRegistry.RegisterHandler(typeof(T)); - } - protected void RegisterOwnedMessageType() - { - router.RegisterMessageRoute(typeof(T), configure.LocalAddress); - } - protected Address RegisterMessageType() - { - var address = new Address(typeof(T).Name, "localhost"); - RegisterMessageType(address); - - return address; - } - - protected void RegisterMessageType(Address address) - { - MessageMapper.Initialize(new[] { typeof(T) }); - MessageSerializer.Initialize(new[] { typeof(T) }); - router.RegisterMessageRoute(typeof(T), address); - MessageMetadataRegistry.RegisterMessageType(typeof(T)); - - } - - protected void StartBus() - { - ((IStartableBus)bus).Start(); - } - - protected void AssertSubscription(Predicate condition, Address addressOfPublishingEndpoint) - { - try - { - messageSender.AssertWasCalled(x => - x.Send(Arg.Matches(m => condition(m)), Arg.Matches(o => o.Destination == addressOfPublishingEndpoint))); - - } - catch (Exception) - { - //retry to avoid race conditions - Thread.Sleep(2000); - messageSender.AssertWasCalled(x => - x.Send(Arg.Matches(m => condition(m)), Arg.Matches(o => o.Destination == addressOfPublishingEndpoint))); - } - } - - protected void AssertSubscription(Address addressOfPublishingEndpoint) - { - try - { - messageSender.AssertWasCalled(x => - x.Send(Arg.Matches(m => IsSubscriptionFor(m)), Arg.Matches(o => o.Destination == addressOfPublishingEndpoint))); - - } - catch (Exception) - { - //retry to avoid race conditions - Thread.Sleep(1000); - messageSender.AssertWasCalled(x => - x.Send(Arg.Matches(m => IsSubscriptionFor(m)), Arg.Matches(o => o.Destination == addressOfPublishingEndpoint))); - } - } - - bool IsSubscriptionFor(TransportMessage transportMessage) - { - var type = Type.GetType(transportMessage.Headers[Headers.SubscriptionMessageType]); - - return type == typeof(T); - } - } - - - class using_the_unicastBus : using_a_configured_unicastBus - { - [SetUp] - public new void SetUp() - { - StartBus(); - } - - protected Exception ResultingException; - - protected void ReceiveMessage(TransportMessage transportMessage) - { - try - { - bus.GetHeaderAction = (o, s) => - { - string v; - transportMessage.Headers.TryGetValue(s, out v); - return v; - }; - - bus.SetHeaderAction = (o, s, v) => { transportMessage.Headers[s] = v; }; - - Transport.FakeMessageBeingProcessed(transportMessage); - } - catch (Exception ex) - { - Console.Out.WriteLine("Fake message processing failed: " + ex); - ResultingException = ex; - } - } - - protected void ReceiveMessage(T message, IDictionary headers = null, MessageMapper mapper = null) - { - RegisterMessageType(); - var messageToReceive = Helpers.Serialize(message, mapper: mapper); - - if (headers != null) - { - foreach (var header in headers) - { - messageToReceive.Headers[header.Key] = header.Value; - } - } - - ReceiveMessage(messageToReceive); - } - - protected void SimulateMessageBeingAbortedDueToRetryCountExceeded(TransportMessage transportMessage) - { - try - { - Transport.FakeMessageBeingPassedToTheFaultManager(transportMessage); - } - catch (Exception ex) - { - ResultingException = ex; - } - } - } -} diff --git a/src/NServiceBus.Core.Tests/Unicast/DeferedMessages.cs b/src/NServiceBus.Core.Tests/Unicast/DeferedMessages.cs deleted file mode 100644 index 717077cfdd9..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/DeferedMessages.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using NUnit.Framework; - using Timeout; - - [TestFixture] - class When_deferring_a_message_with_no_timeoutManager_address_specified : using_the_unicastBus - { - [Test] - public void Should_use_a_convention_to_set_the_address() - { - var conventionBasedAddressToTimeoutManager = MasterNodeAddress.SubScope("Timeouts"); - - RegisterMessageType(); - bus.Defer(TimeSpan.FromDays(1),new DeferredMessage()); - - VerifyThatMessageWasSentTo(conventionBasedAddressToTimeoutManager); - } - } - - [TestFixture] - class When_deferring_a_message_when_involving_worker : using_the_unicastBus - { - [Test] - public void Should_use_master_node_address_when_worker_is_enabled() - { - settings.SetDefault("Worker.Enabled", true); - RegisterMessageType(); - bus.Defer(TimeSpan.FromDays(1), new DeferredMessage()); - VerifyThatMessageWasSentWithHeaders(h => h["NServiceBus.Timeout.RouteExpiredTimeoutTo"] == MasterNodeAddress.ToString()); - } - - [Test] - public void Should_use_local_address_when_worker_is_disabled() - { - settings.SetDefault("Worker.Enabled", false); - RegisterMessageType(); - bus.Defer(TimeSpan.FromDays(1), new DeferredMessage()); - VerifyThatMessageWasSentWithHeaders(h => h["NServiceBus.Timeout.RouteExpiredTimeoutTo"] == configure.LocalAddress.ToString()); - } - } - - [TestFixture] - class When_deferring_a_message_with_a_set_delay : using_the_unicastBus - { - [Test] - public void Should_set_the_expiry_header_to_a_absolute_utc_time() - { - RegisterMessageType(); - var delay = TimeSpan.FromDays(1); - - bus.Defer(delay, new DeferredMessage()); - - VerifyThatMessageWasSentWithHeaders(h=> - { - var e = DateTimeExtensions.ToUtcDateTime(h[TimeoutManagerHeaders.Expire]); - var now = DateTime.UtcNow + delay; - return e <= now; - }); - } - } - - [TestFixture] - class When_deferring_a_message_with_a_absolute_time : using_the_unicastBus - { - [Test] - public void Should_set_the_expiry_header_to_a_absolute_utc_time() - { - RegisterMessageType(); - var time = DateTime.Now + TimeSpan.FromDays(1); - - bus.Defer(time, new DeferredMessage()); - - VerifyThatMessageWasSentWithHeaders(h => h[TimeoutManagerHeaders.Expire] == DateTimeExtensions.ToWireFormattedString(time)); - } - } - - [TestFixture] - class When_short_cutting_deferred_messages_that_already_has_expired : using_the_unicastBus - { - [Test] - public void Should_use_utc_when_comparing() - { - RegisterMessageType(); - var time = DateTime.Now + TimeSpan.FromSeconds(-5); - - bus.Defer(time, new DeferredMessage()); - - //no expiry header should be there since this message will be treated as a send local - VerifyThatMessageWasSentWithHeaders(h => !h.ContainsKey(TimeoutManagerHeaders.Expire)); - } - } - - - public class DeferredMessage:IMessage - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/HandlerInvocationCache.cs b/src/NServiceBus.Core.Tests/Unicast/HandlerInvocationCache.cs index c6e84b42a46..cf1716c5e26 100644 --- a/src/NServiceBus.Core.Tests/Unicast/HandlerInvocationCache.cs +++ b/src/NServiceBus.Core.Tests/Unicast/HandlerInvocationCache.cs @@ -1,147 +1,195 @@ namespace NServiceBus.Unicast.Tests { + using System; using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; + using Pipeline; using NUnit.Framework; - using Saga; + using Testing; [TestFixture] [Explicit("Performance Tests")] - public class HandlerInvocationCachePerformanceTests - { - [Test] - public void RunNew() - { + public class HandlerInvocationCachePerformanceTests + { + [Test] + public async Task RunNew() + { var cache = new MessageHandlerRegistry(new Conventions()); - cache.CacheMethodForHandler(typeof(StubMessageHandler), typeof(StubMessage)); - cache.CacheMethodForHandler(typeof(StubTimeoutHandler), typeof(StubTimeoutState)); - var handler1 = new StubMessageHandler(); - var handler2 = new StubTimeoutHandler(); - var stubMessage1 = new StubMessage(); - var stubMessage2 = new StubTimeoutState(); - cache.InvokeHandle(handler1, stubMessage1); - cache.InvokeHandle(handler2, stubMessage2); - - var startNew = Stopwatch.StartNew(); - for (var i = 0; i < 100000; i++) - { - cache.InvokeHandle(handler1, stubMessage1); - cache.InvokeHandle(handler2, stubMessage2); - } - startNew.Stop(); - Trace.WriteLine(startNew.ElapsedMilliseconds); - } - - public class StubMessageHandler : IHandleMessages - { - - public void Handle(StubMessage message) - { - } - } - - public class StubMessage - { - } - - public class StubTimeoutHandler : IHandleTimeouts - { - public void Timeout(StubTimeoutState state) - { - } - } - - public class StubTimeoutState - { - } - } - - [TestFixture] - public class When_invoking_a_cached_message_handler - { - [Test] - public void Should_invoke_handle_method() - { + cache.RegisterHandler(typeof(StubMessageHandler)); + cache.RegisterHandler(typeof(StubTimeoutHandler)); + var stubMessage = new StubMessage(); + var stubTimeout = new StubTimeoutState(); + var messageHandler = cache.GetCachedHandlerForMessage(); + var timeoutHandler = cache.GetCachedHandlerForMessage(); + + await messageHandler.Invoke(stubMessage, null); + await timeoutHandler.Invoke(stubTimeout, null); + + var startNew = Stopwatch.StartNew(); + for (var i = 0; i < 100000; i++) + { + await messageHandler.Invoke(stubMessage, null); + await timeoutHandler.Invoke(stubTimeout, null); + } + startNew.Stop(); + Trace.WriteLine(startNew.ElapsedMilliseconds); + } + + public class StubMessageHandler : IHandleMessages + { + public Task Handle(StubMessage message, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + } + + public class StubMessage : IMessage + { + } + + public class StubTimeoutHandler : IHandleTimeouts + { + public Task Timeout(StubTimeoutState state, IMessageHandlerContext context) + { + return TaskEx.CompletedTask; + } + } + + public class StubTimeoutState : IMessage + { + } + } + + [TestFixture] + public class When_invoking_a_cached_message_handler + { + [Test] + public async Task Should_invoke_handle_method() + { var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); - cache.CacheMethodForHandler(typeof(StubHandler), typeof(StubMessage)); - var handler = new StubHandler(); - cache.InvokeHandle(handler, new StubMessage()); - Assert.IsTrue(handler.HandleCalled); - } + var handler = cache.GetCachedHandlerForMessage(); + await handler.Invoke(new StubMessage(), null); - [Test] - public void Should_have_passed_through_correct_message() - { - var cache = new MessageHandlerRegistry(new Conventions()); + Assert.IsTrue(((StubHandler)handler.Instance).HandleCalled); + } - cache.CacheMethodForHandler(typeof(StubHandler), typeof(StubMessage)); - var handler = new StubHandler(); - var stubMessage = new StubMessage(); - cache.InvokeHandle(handler, stubMessage); - Assert.AreEqual(stubMessage, handler.HandledMessage); - } - - public class StubHandler : IHandleMessages - { - public bool HandleCalled; - public StubMessage HandledMessage; - - public void Handle(StubMessage message) - { - HandleCalled = true; - HandledMessage = message; - } - } - - public class StubMessage - { - } - } - - [TestFixture] - public class When_invoking_a_cached_timeout_handler - { - [Test] - public void Should_invoke_timeout_method() - { + [Test] + public async Task Should_have_passed_through_correct_message() + { var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); - cache.CacheMethodForHandler(typeof(StubHandler), typeof(StubTimeoutState)); - var handler = new StubHandler(); - cache.InvokeTimeout(handler, new StubTimeoutState()); - Assert.IsTrue(handler.TimeoutCalled); - } + var handler = cache.GetCachedHandlerForMessage(); + var stubMessage = new StubMessage(); + await handler.Invoke(stubMessage, null); - [Test] - public void Should_have_passed_through_correct_state() - { - var cache = new MessageHandlerRegistry(new Conventions()); + Assert.AreEqual(stubMessage, ((StubHandler)handler.Instance).HandledMessage); + } - cache.CacheMethodForHandler(typeof(StubHandler), typeof(StubTimeoutState)); - var handler = new StubHandler(); - var stubState = new StubTimeoutState(); - cache.InvokeTimeout(handler, stubState); - Assert.AreEqual(stubState, handler.HandledState); - } + [Test] + public async Task Should_have_passed_through_correct_context() + { + var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); + + var handler = cache.GetCachedHandlerForMessage(); + var handlerContext = new TestableMessageHandlerContext(); + await handler.Invoke(new StubMessage(), handlerContext); + + Assert.AreSame(handlerContext, ((StubHandler)handler.Instance).HandlerContext); + } + + public class StubHandler : IHandleMessages + { + public Task Handle(StubMessage message, IMessageHandlerContext context) + { + HandleCalled = true; + HandledMessage = message; + HandlerContext = context; + return TaskEx.CompletedTask; + } + + public bool HandleCalled; + public StubMessage HandledMessage; + public IMessageHandlerContext HandlerContext; + } + + public class StubMessage : IMessage + { + } + } - public class StubHandler : IHandleTimeouts - { - public bool TimeoutCalled; - public StubTimeoutState HandledState; + [TestFixture] + public class When_invoking_a_cached_timeout_handler + { + [Test] + public async Task Should_invoke_timeout_method() + { + var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); + var handler = cache.GetCachedHandlerForMessage(); + await handler.Invoke(new StubTimeoutState(), null); - public void Timeout(StubTimeoutState state) - { - TimeoutCalled = true; - HandledState = state; - } - } + Assert.IsTrue(((StubHandler)handler.Instance).TimeoutCalled); + } - public class StubTimeoutState - { - } + [Test] + public async Task Should_have_passed_through_correct_state() + { + var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); - } + var stubState = new StubTimeoutState(); + var handler = cache.GetCachedHandlerForMessage(); + await handler.Invoke(stubState, null); -} + Assert.AreEqual(stubState, ((StubHandler)handler.Instance).HandledState); + } + [Test] + public async Task Should_have_passed_through_correct_context() + { + var cache = new MessageHandlerRegistry(new Conventions()); + cache.RegisterHandler(typeof(StubHandler)); + + var handler = cache.GetCachedHandlerForMessage(); + var handlerContext = new TestableMessageHandlerContext(); + await handler.Invoke(new StubTimeoutState(), handlerContext); + + Assert.AreSame(handlerContext, ((StubHandler)handler.Instance).HandlerContext); + } + + public class StubHandler : IHandleTimeouts + { + public Task Timeout(StubTimeoutState state, IMessageHandlerContext context) + { + TimeoutCalled = true; + HandledState = state; + HandlerContext = context; + return TaskEx.CompletedTask; + } + + public StubTimeoutState HandledState; + public bool TimeoutCalled; + public IMessageHandlerContext HandlerContext; + } + + public class StubTimeoutState : IMessage + { + } + } + + static class MessageHandlerRegistryExtension + { + public static MessageHandler GetCachedHandlerForMessage(this MessageHandlerRegistry cache) + { + var handler = cache.GetHandlersFor(typeof(TMessage)).Single(); + handler.Instance = Activator.CreateInstance(handler.HandlerType); + return handler; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeSubscriptionStorage.cs b/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeSubscriptionStorage.cs deleted file mode 100644 index 264b4a76f9d..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeSubscriptionStorage.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Helpers -{ - using System.Collections.Generic; - using Subscriptions; - using Subscriptions.MessageDrivenSubscriptions; - - public class FakeSubscriptionStorage : ISubscriptionStorage - { - public void Subscribe(Address address, IEnumerable messageTypes) - { - foreach (var messageType in messageTypes) - { - if (!storage.ContainsKey(messageType)) - storage[messageType] = new List
(); - - if (!storage[messageType].Contains(address)) - storage[messageType].Add(address); - } - } - - public void Unsubscribe(Address address, IEnumerable messageTypes) - { - foreach (var messageType in messageTypes) - { - if (storage.ContainsKey(messageType)) - storage[messageType].Remove(address); - } - } - - public IEnumerable
GetSubscriberAddressesForMessage(IEnumerable messageTypes) - { - var result = new List
(); - foreach (var m in messageTypes) - { - if (storage.ContainsKey(m)) - result.AddRange(storage[m]); - } - - return result; - } - public void FakeSubscribe(Address address) - { - ((ISubscriptionStorage)this).Subscribe(address, new[] { new MessageType(typeof(T)) }); - } - - public void Init() - { - } - - readonly Dictionary> storage = new Dictionary>(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeTransport.cs b/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeTransport.cs deleted file mode 100644 index 00b0649e895..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Helpers/FakeTransport.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Helpers -{ - using System; - using Transport; - - public class FakeTransport : ITransport - { - public void Dispose() - { - - } - - public void Start(Address localAddress) - { - } - - public int MaximumConcurrencyLevel - { - get { return 1; } - } - - public void ChangeMaximumConcurrencyLevel(int maximumConcurrencyLevel) - { - - } - - public void AbortHandlingCurrentMessage() - { - - } - - public void Stop() - { - } - - public void ChangeMaximumMessageThroughputPerSecond(int maximumMessageThroughputPerSecond) - { - throw new NotImplementedException(); - } - - public event EventHandler TransportMessageReceived; - public event EventHandler StartedMessageProcessing; - public event EventHandler FinishedMessageProcessing; - - public event EventHandler FailedMessageProcessing; - - public void FakeMessageBeingProcessed(TransportMessage transportMessage) - { - StartedMessageProcessing(this, new StartedMessageProcessingEventArgs(transportMessage)); - TransportMessageReceived(this,new TransportMessageReceivedEventArgs(transportMessage)); - FinishedMessageProcessing(this, new FinishedMessageProcessingEventArgs(transportMessage)); - } - - public void FakeMessageBeingPassedToTheFaultManager(TransportMessage transportMessage) - { - try - { - StartedMessageProcessing(this, new StartedMessageProcessingEventArgs(transportMessage)); - } - catch(Exception ex) - { - if (FailedMessageProcessing != null) - FailedMessageProcessing(this, new FailedMessageProcessingEventArgs(transportMessage, ex)); - } - FinishedMessageProcessing(this, new FinishedMessageProcessingEventArgs(transportMessage)); - } - public int MaximumMessageThroughputPerSecond { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Helpers/Helpers.cs b/src/NServiceBus.Core.Tests/Unicast/Helpers/Helpers.cs deleted file mode 100644 index 754d0ddf70c..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Helpers/Helpers.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NServiceBus.Unicast.Tests.Helpers -{ - using System; - using System.Collections.Generic; - using System.IO; - using MessageInterfaces.MessageMapper.Reflection; - using Serializers.XML; - - class Helpers - { - public static TransportMessage EmptyTransportMessage(Address replyToAddress = null) - { - if (replyToAddress == null) - { - replyToAddress = Address.Parse("myClient"); - } - return new TransportMessage(Guid.NewGuid().ToString(),new Dictionary{{Headers.ReplyToAddress,replyToAddress.ToString()}}); - } - - public static TransportMessage EmptySubscriptionMessage() - { - var subscriptionMessage = new TransportMessage(Guid.NewGuid().ToString(), new Dictionary { { Headers.ReplyToAddress, "mySubscriber" } }) { MessageIntent = MessageIntentEnum.Subscribe }; - - subscriptionMessage.Headers[Headers.SubscriptionMessageType] = - "NServiceBus.Unicast.Tests.MyMessage, Version=3.0.0.0, Culture=neutral, PublicKeyToken=9fc386479f8a226c"; - - return subscriptionMessage; - } - - public static TransportMessage MessageThatFailsToSerialize() - { - var m = EmptyTransportMessage(); - m.Body = new byte[1]; - return m; - } - - public static TransportMessage Serialize(T message, bool nullReplyToAddress = false, MessageMapper mapper = null) - { - var s = new XmlMessageSerializer(mapper ?? new MessageMapper(), new Conventions()); - - s.Initialize(new[] { typeof(T) }); - - var m = EmptyTransportMessage(); - - if (nullReplyToAddress) - { - m = new TransportMessage(); - } - - using (var stream = new MemoryStream()) - { - s.Serialize(message, stream); - m.Body = stream.ToArray(); - } - - m.Headers[Headers.EnclosedMessageTypes] = typeof(T).FullName; - - - return m; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/LoadHandlersBehaviorTests.cs b/src/NServiceBus.Core.Tests/Unicast/LoadHandlersBehaviorTests.cs new file mode 100644 index 00000000000..39bc40feb68 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Unicast/LoadHandlersBehaviorTests.cs @@ -0,0 +1,24 @@ +namespace NServiceBus.Unicast.Tests +{ + using Outbox; + using NServiceBus.Transport; + using NUnit.Framework; + using Testing; + + [TestFixture] + public class LoadHandlersBehaviorTests + { + [Test] + public void Should_throw_when_there_are_no_registered_message_handlers() + { + var behavior = new LoadHandlersConnector(new MessageHandlerRegistry(new Conventions()), new InMemorySynchronizedStorage(), new InMemoryTransactionalSynchronizedStorageAdapter()); + + var context = new TestableIncomingLogicalMessageContext(); + + context.Extensions.Set(new InMemoryOutboxTransaction()); + context.Extensions.Set(new TransportTransaction()); + + Assert.That(async () => await behavior.Invoke(context, c => TaskEx.CompletedTask), Throws.InvalidOperationException); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/MessageOperationsTests.cs b/src/NServiceBus.Core.Tests/Unicast/MessageOperationsTests.cs new file mode 100644 index 00000000000..54cdccd6806 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Unicast/MessageOperationsTests.cs @@ -0,0 +1,131 @@ +namespace NServiceBus.Unicast.Tests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using MessageInterfaces; + using MessageInterfaces.MessageMapper.Reflection; + using NUnit.Framework; + using Pipeline; + using Testing; + using PublishOptions = NServiceBus.PublishOptions; + using ReplyOptions = NServiceBus.ReplyOptions; + using SendOptions = NServiceBus.SendOptions; + + [TestFixture] + public class MessageOperationsTests + { + [Test] + public void When_sending_message_interface_should_set_interface_as_message_type() + { + var sendPipeline = new FakePipeline(); + var context = CreateContext(sendPipeline); + + MessageOperations.Send(context, m => { }, new SendOptions()); + + Assert.That(sendPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(IMyMessage))); + } + + [Test] + public void When_sending_message_class_should_set_class_as_message_type() + { + var sendPipeline = new FakePipeline(); + var context = CreateContext(sendPipeline); + + MessageOperations.Send(context, m => { }, new SendOptions()); + + Assert.That(sendPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(MyMessage))); + } + + [Test] + public void When_replying_message_interface_should_set_interface_as_message_type() + { + var replyPipeline = new FakePipeline(); + var context = CreateContext(replyPipeline); + + MessageOperations.Reply(context, m => { }, new ReplyOptions()); + + Assert.That(replyPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(IMyMessage))); + } + + [Test] + public void When_replying_message_class_should_set_class_as_message_type() + { + var replyPipeline = new FakePipeline(); + var context = CreateContext(replyPipeline); + + MessageOperations.Reply(context, m => { }, new ReplyOptions()); + + Assert.That(replyPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(MyMessage))); + } + + [Test] + public void When_publishing_event_interface_should_set_interface_as_message_type() + { + var publishPipeline = new FakePipeline(); + var context = CreateContext(publishPipeline); + + MessageOperations.Publish(context, m => { }, new PublishOptions()); + + Assert.That(publishPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(IMyMessage))); + } + + [Test] + public void When_publishing_event_class_should_set_class_as_message_type() + { + var publishPipeline = new FakePipeline(); + var context = CreateContext(publishPipeline); + + MessageOperations.Publish(context, m => { }, new PublishOptions()); + + Assert.That(publishPipeline.ReceivedContext.Message.MessageType, Is.EqualTo(typeof(MyMessage))); + } + + IBehaviorContext CreateContext(IPipeline pipeline) where TContext : IBehaviorContext + { + var pipelineCache = new FakePipelineCache(); + pipelineCache.RegisterPipeline(pipeline); + + var context = new TestableMessageHandlerContext(); + context.Builder.Register(() => new MessageMapper()); + context.Extensions.Set(pipelineCache); + + return context; + } + + // ReSharper disable once MemberCanBePrivate.Global + public interface IMyMessage + { + } + + class MyMessage + { + } + + class FakePipelineCache : IPipelineCache + { + Dictionary pipelines = new Dictionary(); + + public void RegisterPipeline(IPipeline pipeline) where TContext : IBehaviorContext + { + pipelines.Add(typeof(TContext), pipeline); + } + + public IPipeline Pipeline() where TContext : IBehaviorContext + { + return pipelines[typeof(TContext)] as IPipeline; + } + } + + class FakePipeline : IPipeline where TContext : IBehaviorContext + { + public TContext ReceivedContext { get; set; } + + public Task Invoke(TContext context) + { + ReceivedContext = context; + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/MessageTypeTests.cs b/src/NServiceBus.Core.Tests/Unicast/MessageTypeTests.cs new file mode 100644 index 00000000000..9b18f2567f6 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Unicast/MessageTypeTests.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.Unicast.Tests +{ + using System; + using NUnit.Framework; + + [TestFixture] + public class MessageTypeTests + { + [Test] + public void Should_parse_types() + { + var messageType = new Subscriptions.MessageType(typeof(TestMessage)); + + Assert.AreEqual(messageType.TypeName, typeof(TestMessage).FullName); + Assert.AreEqual(messageType.Version, typeof(TestMessage).Assembly.GetName().Version); + } + + [Test] + public void Should_parse_AssemblyQualifiedName() + { + var messageType = new Subscriptions.MessageType(typeof(TestMessage).AssemblyQualifiedName); + + Assert.AreEqual(messageType.TypeName, typeof(TestMessage).FullName); + Assert.AreEqual(messageType.Version, typeof(TestMessage).Assembly.GetName().Version); + } + + [Test] + public void Should_parse_version_strings() + { + var messageType = new Subscriptions.MessageType("TestMessage", "1.2.3.4"); + + Assert.AreEqual(messageType.TypeName, "TestMessage"); + Assert.AreEqual(messageType.Version, new Version(1, 2, 3, 4)); + } + + + class TestMessage + { + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Messages/DefaultMessageRegistryTests.cs b/src/NServiceBus.Core.Tests/Unicast/Messages/DefaultMessageRegistryTests.cs index d9042574c08..c53820e5d24 100644 --- a/src/NServiceBus.Core.Tests/Unicast/Messages/DefaultMessageRegistryTests.cs +++ b/src/NServiceBus.Core.Tests/Unicast/Messages/DefaultMessageRegistryTests.cs @@ -1,6 +1,7 @@ namespace NServiceBus.Unicast.Tests { using System; + using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Unicast.Messages; @@ -14,16 +15,21 @@ public class When_getting_message_definition [Test] public void Should_throw_an_exception_for_a_unmapped_type() { - var defaultMessageRegistry = new MessageMetadataRegistry(false, new Conventions()); + var defaultMessageRegistry = new MessageMetadataRegistry(new Conventions()); Assert.Throws(() => defaultMessageRegistry.GetMessageMetadata(typeof(int))); } [Test] public void Should_return_metadata_for_a_mapped_type() { - var defaultMessageRegistry = new MessageMetadataRegistry(false, new Conventions()); - defaultMessageRegistry.RegisterMessageType(typeof(int)); + var conventions = new Conventions(); + conventions.IsMessageTypeAction = type => type == typeof(int); + + var defaultMessageRegistry = new MessageMetadataRegistry(conventions); + defaultMessageRegistry.RegisterMessageTypesFoundIn(new List { typeof(int) }); + var messageMetadata = defaultMessageRegistry.GetMessageMetadata(typeof(int)); + Assert.AreEqual(typeof(int), messageMetadata.MessageType); Assert.AreEqual(1, messageMetadata.MessageHierarchy.Count()); } @@ -32,9 +38,9 @@ public void Should_return_metadata_for_a_mapped_type() [Test] public void Should_return_the_correct_parent_hierarchy() { - var defaultMessageRegistry = new MessageMetadataRegistry(false, new Conventions()); + var defaultMessageRegistry = new MessageMetadataRegistry(new Conventions()); - defaultMessageRegistry.RegisterMessageType(typeof(MyEvent)); + defaultMessageRegistry.RegisterMessageTypesFoundIn(new List { typeof(MyEvent) }); var messageMetadata = defaultMessageRegistry.GetMessageMetadata(typeof(MyEvent)); Assert.AreEqual(5, messageMetadata.MessageHierarchy.Count()); @@ -44,7 +50,7 @@ public void Should_return_the_correct_parent_hierarchy() Assert.AreEqual(typeof(ConcreteParent1), messageMetadata.MessageHierarchy.ToList()[2]); Assert.AreEqual(typeof(InterfaceParent1Base), messageMetadata.MessageHierarchy.ToList()[3]); Assert.AreEqual(typeof(ConcreteParentBase), messageMetadata.MessageHierarchy.ToList()[4]); - + } @@ -67,7 +73,7 @@ interface InterfaceParent1 : InterfaceParent1Base } - interface InterfaceParent1Base : IMessage + interface InterfaceParent1Base : IMessage { } diff --git a/src/NServiceBus.Core.Tests/Unicast/MessagingBestPracticesTests.cs b/src/NServiceBus.Core.Tests/Unicast/MessagingBestPracticesTests.cs index 84c7a5ed677..517fd0820cd 100644 --- a/src/NServiceBus.Core.Tests/Unicast/MessagingBestPracticesTests.cs +++ b/src/NServiceBus.Core.Tests/Unicast/MessagingBestPracticesTests.cs @@ -12,34 +12,24 @@ public class When_replying [Test] public void Should_throw_for_command() { - var invalidOperationException = Assert.Throws(() => MessagingBestPractices.AssertIsValidForReply(typeof(MyCommand), new Conventions())); - Assert.AreEqual("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner using bus.Send and bus. Events should be Published with bus.Publish.", invalidOperationException.Message); + var invalidOperationException = Assert.Throws(() => + new Validations(new Conventions()).AssertIsValidForReply(typeof(MyCommand))); + Assert.AreEqual("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner. Events should be published.", invalidOperationException.Message); } [Test] public void Should_throw_for_event() { - var invalidOperationException = Assert.Throws(() => MessagingBestPractices.AssertIsValidForReply(typeof(MyEvent), new Conventions())); - Assert.AreEqual("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner using bus.Send and bus. Events should be Published with bus.Publish.", invalidOperationException.Message); + var invalidOperationException = Assert.Throws(() => + new Validations(new Conventions()).AssertIsValidForReply(typeof(MyEvent))); + Assert.AreEqual("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner. Events should be published.", invalidOperationException.Message); } [Test] public void Should_not_throw_for_message() { - MessagingBestPractices.AssertIsValidForReply(typeof(MyMessage), new Conventions()); - } - - public class MyMessage : IMessage - { - - } - public class MyCommand : ICommand - { - - } - public class MyEvent : IEvent - { - + new Validations(new Conventions()) + .AssertIsValidForReply(typeof(MyMessage)); } } @@ -49,35 +39,39 @@ public class When_pubsub [Test] public void Should_throw_for_command() { - var invalidOperationException = Assert.Throws(() => MessagingBestPractices.AssertIsValidForPubSub(typeof(MyCommand), new Conventions())); + var invalidOperationException = Assert.Throws(() => + new Validations(new Conventions()).AssertIsValidForPubSub(typeof(MyCommand))); Assert.AreEqual("Pub/Sub is not supported for Commands. They should be be sent direct to their logical owner.", invalidOperationException.Message); } [Test] public void Should_not_throw_for_event() { - MessagingBestPractices.AssertIsValidForPubSub(typeof(MyEvent), new Conventions()); - //TODO: verify log + new Validations(new Conventions()).AssertIsValidForPubSub(typeof(MyEvent)); } [Test] public void Should_not_throw_for_message() { - MessagingBestPractices.AssertIsValidForPubSub(typeof(MyMessage), new Conventions()); + new Validations(new Conventions()).AssertIsValidForPubSub(typeof(MyMessage)); } + } - public class MyMessage : IMessage - { - - } - public class MyCommand : ICommand - { - - } - public class MyEvent : IEvent - { - - } + public class MyMessage : IMessage + { + } + + public class MyCommand : ICommand + { + } + + public class MyEvent : IEvent + { + } + + [TimeToBeReceived("00:00:01")] + public class MyDeferredMessage : IMessage + { } } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Publishing.cs b/src/NServiceBus.Core.Tests/Unicast/Publishing.cs deleted file mode 100644 index a5b5200bc8d..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Publishing.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using NUnit.Framework; - using Rhino.Mocks; - - [TestFixture] - class When_publishing_a_command : using_the_unicastBus - { - [Test] - public void Should_get_an_error_message() - { - RegisterMessageType(); - - Assert.Throws(() => bus.Publish(new CommandMessage())); - } - } - - [TestFixture] - class When_publishing_an_event : using_the_unicastBus - { - [Test] - public void Should_send_a_message_to_each_subscriber() - { - var subscriber1 = new Address("sub1", "."); - var subscriber2 = new Address("sub2", "."); - - RegisterMessageType(); - subscriptionStorage.FakeSubscribe(subscriber1); - subscriptionStorage.FakeSubscribe(subscriber2); - - bus.Publish(new EventMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Is.Anything, Arg.Matches(o=>o.Destination == subscriber1))); - - messageSender.AssertWasCalled(x => x.Send(Arg.Is.Anything, Arg.Matches(o => o.Destination == subscriber2))); - } - } -} diff --git a/src/NServiceBus.Core.Tests/Unicast/Receiving.cs b/src/NServiceBus.Core.Tests/Unicast/Receiving.cs deleted file mode 100644 index 4d77c40ac92..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Receiving.cs +++ /dev/null @@ -1,238 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using Contexts; - using NUnit.Framework; - using Rhino.Mocks; - using Saga; - - [TestFixture] - class When_receiving_a_regular_message : using_the_unicastBus - { - [Test] - public void Should_invoke_the_registered_message_handlers() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - RegisterMessageType(); - RegisterMessageHandlerType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - - Assert.True(Handler1.Called); - Assert.True(Handler2.Called); - } - } - - [TestFixture] - class When_receiving_any_message : using_the_unicastBus - { - [Test] - public void Should_invoke_the_registered_catch_all_handler_using_a_object_parameter() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - RegisterMessageType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - Assert.True(CatchAllHandler_object.Called); - } - - [Test] - public void Should_throw_when_there_are_no_registered_message_handlers() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - RegisterMessageType(); - ReceiveMessage(receivedMessage); - Assert.IsNotNull(ResultingException, "When no handlers are found and a message ends up in the endpoint, an exception should be thrown"); - Assert.That(ResultingException.GetBaseException().Message, Contains.Substring(typeof(EventMessage).ToString()), "The exception message should be meaningful and should inform the user the message type for which a handler could not be found."); - } - - [Test] - public void Should_invoke_the_registered_catch_all_handler_using_a_dynamic_parameter() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - RegisterMessageType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - Assert.True(CatchAllHandler_dynamic.Called); - } - - [Test] - public void Should_invoke_the_registered_catch_all_handler_using_a_iMessage_parameter() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - RegisterMessageType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - Assert.True(CatchAllHandler_IMessage.Called); - } - } - - [TestFixture] - class When_sending_messages_from_a_messageHandler : using_the_unicastBus - { - [Test] - public void Should_set_the_related_to_header_with_the_id_of_the_current_message() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - receivedMessage.CorrelationId = receivedMessage.Id; - - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Headers[Headers.RelatedTo] == receivedMessage.CorrelationId), Arg.Is.Anything)); - } - } - - [TestFixture] - class When_replying_with_a_command : using_the_unicastBus - { - [Test] - public void Should_not_be_allowed() - { - var receivedMessage = Helpers.Helpers.Serialize(new EventMessage()); - - RegisterMessageType(); - RegisterMessageType(); - RegisterMessageHandlerType(); - - ReceiveMessage(receivedMessage); - - messageSender.AssertWasNotCalled(x => x.Send(Arg.Is.Anything, Arg.Is.Anything)); - } - } - - [TestFixture] - class When_receiving_a_v3_saga_timeout_message - { - [Test,Ignore("Move to a acceptance test")] - public void Should_set_the_newV4_flag() - { - //var sagaId = Guid.NewGuid(); - - //RegisterSaga(new MySagaData - //{ - // Id = sagaId - //}); - - //ReceiveMessage(new MyTimeout(), new Dictionary - //{ - // {Headers.NServiceBusVersion, "3.3.8"}, - // {Headers.SagaId, sagaId.ToString()}, - // {TimeoutManagerHeaders.Expire, "2013-06-20 03:41:00:188412 Z"} - //}, mapper: MessageMapper); - - //Assert.AreEqual(1, persister.CurrentSagaEntities.Count, "Existing saga should be found"); - //Assert.True(((MySagaData)persister.CurrentSagaEntities[sagaId].SagaEntity).TimeoutCalled, "Timeout method should be invoked"); - } - - class MySaga : Saga, IHandleTimeouts, IHandleMessages - { - public void Timeout(MyTimeout timeout) - { - Data.TimeoutCalled = true; - } - - public void Handle(MyTimeout message) - { - Assert.Fail("Regular handler should not be invoked"); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - } - } - - class MySagaData : ContainSagaData - { - public bool TimeoutCalled { get; set; } - } - - class MyTimeout : IMessage { } - - } - - class HandlerThatRepliesWithACommand : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(EventMessage message) - { - Bus.Reply(new CommandMessage()); - } - } - - class HandlerThatSendsAMessage : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(EventMessage message) - { - Bus.Send(new CommandMessage()); - } - } - - class Handler1:IHandleMessages - { - public static bool Called; - - public void Handle(EventMessage message) - { - Called = true; - } - } - - class Handler2 : IHandleMessages - { - public static bool Called; - - public void Handle(EventMessage message) - { - Called = true; - } - } - - public class CatchAllHandler_object:IHandleMessages - { - public static bool Called; - - public void Handle(object message) - { - Called = true; - } - } - - public class CatchAllHandler_dynamic : IHandleMessages - { - public static bool Called; - - public void Handle(dynamic message) - { - Called = true; - } - } - - public class CatchAllHandler_IMessage : IHandleMessages - { - public static bool Called; - - public void Handle(IMessage message) - { - Called = true; - } - } -} diff --git a/src/NServiceBus.Core.Tests/Unicast/RunningEndpointInstanceTest.cs b/src/NServiceBus.Core.Tests/Unicast/RunningEndpointInstanceTest.cs new file mode 100644 index 00000000000..faeed076541 --- /dev/null +++ b/src/NServiceBus.Core.Tests/Unicast/RunningEndpointInstanceTest.cs @@ -0,0 +1,62 @@ +namespace NServiceBus.Unicast.Tests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Features; + using NServiceBus.Routing; + using Settings; + using NUnit.Framework; + using Testing; + using NServiceBus.Transport; + + [TestFixture] + public class RunningEndpointInstanceTest + { + [Test] + public async Task ShouldAllowMultipleStops() + { + var testee = new RunningEndpointInstance( + new SettingsHolder(), + new FakeBuilder(), + new List(), + new FeatureRunner(new FeatureActivator(new SettingsHolder())), + new MessageSession(new RootContext(null, null, null)), new FakeTransportInfrastructure()); + + await testee.Stop(); + + Assert.That(async () => await testee.Stop(), Throws.Nothing); + } + + class FakeTransportInfrastructure : TransportInfrastructure + { + public override IEnumerable DeliveryConstraints { get; } + public override TransportTransactionMode TransactionMode { get; } + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } + public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() + { + throw new NotImplementedException(); + } + + public override TransportSendInfrastructure ConfigureSendInfrastructure() + { + throw new NotImplementedException(); + } + + public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() + { + throw new NotImplementedException(); + } + + public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) + { + throw new NotImplementedException(); + } + + public override string ToTransportAddress(LogicalAddress logicalAddress) + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/SendOnlyMode.cs b/src/NServiceBus.Core.Tests/Unicast/SendOnlyMode.cs deleted file mode 100644 index cf42c4548e4..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/SendOnlyMode.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using NUnit.Framework; - - [TestFixture] - class When_sending_a_message_in_send_only_mode : using_a_configured_unicastBus - { - [Test] - public void Should_be_allowed() - { - settings.Set("Endpoint.SendOnly", true); - RegisterMessageType(); - bus.Send(configure.LocalAddress, new TestMessage()); - } - } - [TestFixture] - class When_subscribing_to_a_message_in_send_only_mode : using_a_configured_unicastBus - { - [Test] - public void Should_not_be_allowed() - { - settings.Set("Endpoint.SendOnly", true); - RegisterMessageType(); - Assert.Throws(() => bus.Subscribe()); - } - } - - [TestFixture] - class When_unsubscribing_to_a_message_in_send_only_mode : using_a_configured_unicastBus - { - [Test] - public void Should_not_be_allowed() - { - settings.Set("Endpoint.SendOnly", true); - - RegisterMessageType(); - Assert.Throws(() => bus.Unsubscribe()); - } - } - - [TestFixture] - class When_replying_to_a_message_that_was_sent_with_null_reply_to_address : using_the_unicastBus - { - [Test] - public void Should_blow() - { - RegisterMessageType(); - var receivedMessage = Helpers.Helpers.Serialize(new TestMessage(),true); - RegisterMessageHandlerType(); - ReceiveMessage(receivedMessage); - Assert.IsInstanceOf(ResultingException.GetBaseException()); - } - } - - [TestFixture] - class When_returning_to_a_message_that_was_sent_with_null_reply_to_address : using_the_unicastBus - { - [Test] - public void Should_blow() - { - RegisterMessageType(); - var receivedMessage = Helpers.Helpers.Serialize(new TestMessage(),true); - RegisterMessageHandlerType(); - ReceiveMessage(receivedMessage); - Assert.IsInstanceOf(ResultingException.GetBaseException()); - } - } - - public class TestMessage : IMessage - { - } - - class HandlerThatRepliesWithACommandToAMessage : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(TestMessage message) - { - Bus.Reply(new TestMessage()); - } - } - - class HandlerThatReturns : IHandleMessages - { - public IBus Bus { get; set; } - - public void Handle(TestMessage message) - { - Bus.Return(1); - } - } - -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Sending.cs b/src/NServiceBus.Core.Tests/Unicast/Sending.cs deleted file mode 100644 index d0b9c83f214..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Sending.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using NUnit.Framework; - using Rhino.Mocks; - - [TestFixture] - class When_sending_a_message_with_databusProperty : using_the_unicastBus - { - [Test] - public void Should_sent_if_only_one_message_is_in_the_same_send() - { - RegisterMessageType(); - - bus.Send(new CommandWithDataBusPropertyMessage()); - } - } - - [TestFixture] - class When_sending_a_event_message : using_the_unicastBus - { - [Test] - public void Should_get_an_error_messages() - { - RegisterMessageType(); - Assert.Throws(() => bus.Send(new EventMessage())); - } - } - - [TestFixture] - class When_sending_any_message : using_the_unicastBus - { - [Test] - public void The_content_type_should_be_set() - { - bus.OutgoingHeaders["MyStaticHeader"] = "StaticHeaderValue"; - RegisterMessageType(); - bus.Send(new TestMessage()); - - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => - m.Headers[Headers.ContentType] == "text/xml" && - m.Headers["MyStaticHeader"] == "StaticHeaderValue"), Arg.Is.Anything)); - } - - [Test] - public void It_should_be_persistent_by_default() - { - RegisterMessageType(); - bus.Send(new TestMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Recoverable), Arg.Is.Anything)); - } - - [Test] - public void Should_set_the_reply_to_address() - { - RegisterMessageType(); - bus.Send(new TestMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Is.Anything, Arg.Matches(o => o.ReplyToAddress == configure.LocalAddress))); - } - - [Test] - public void Should_generate_a_conversation_id() - { - RegisterMessageType(); - bus.Send(new TestMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Headers.ContainsKey(Headers.ConversationId)), Arg.Is.Anything)); - } - - [Test] - public void Should_not_override_a_conversation_id_specified_by_the_user() - { - RegisterMessageType(); - - - bus.Send(m => bus.SetMessageHeader(m, Headers.ConversationId, "my order id")); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Headers[Headers.ConversationId] == "my order id"), Arg.Is.Anything)); - } - - [Test, Ignore("Needs refactoring to make testing possible")] - public void Should_propagate_the_incoming_replyTo_address_if_requested() - { - var addressOfIncomingMessage = Address.Parse("Incoming"); - - //todo - add a way to set the context from out tests - - bus.PropagateReturnAddressOnSend = true; - RegisterMessageType(); - bus.Send(new TestMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.ReplyToAddress == addressOfIncomingMessage), Arg.Is.Anything)); - } - } - - [TestFixture] - class When_sending_multiple_messages_in_one_go : using_the_unicastBus - { - - [Test] - public void Should_be_persistent_if_any_of_the_messages_is_persistent() - { - RegisterMessageType(configure.LocalAddress); - RegisterMessageType(configure.LocalAddress); - bus.Send(new NonPersistentMessage()); - bus.Send(new PersistentMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Recoverable), Arg.Is.Anything)); - } - - - [Test] - public void Should_use_the_lowest_time_to_be_received() - { - RegisterMessageType(configure.LocalAddress); - RegisterMessageType(configure.LocalAddress); - bus.Send(new NonPersistentMessage()); - bus.Send(new PersistentMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.TimeToBeReceived == TimeSpan.FromMinutes(45)), Arg.Is.Anything)); - } - - [TimeToBeReceived("00:45:00")] - class PersistentMessage { } - - [Express] - class NonPersistentMessage { } - } - - - - [TestFixture] - class When_sending_any_message_from_a_volatile_endpoint : using_the_unicastBus - { - [Test] - public void It_should_be_non_persistent_by_default() - { - MessageMetadataRegistry.DefaultToNonPersistentMessages = true; - RegisterMessageType(); - bus.Send(new TestMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => !m.Recoverable), Arg.Is.Anything)); - } - } - - [TestFixture] - class When_sending_a_command_message : using_the_unicastBus - { - [Test] - public void Should_specify_the_message_to_be_recoverable() - { - RegisterMessageType(); - - bus.Send(new CommandMessage()); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Recoverable), Arg.Is.Anything)); - } - } - - [TestFixture] - class When_sending_a_interface_message : using_the_unicastBus - { - [Test] - public void Should_specify_the_message_to_be_recoverable() - { - var defaultAddress = RegisterMessageType(); - - bus.Send(m => { }); - - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Recoverable), Arg.Matches(o=>o.Destination == defaultAddress))); - } - } -} diff --git a/src/NServiceBus.Core.Tests/Unicast/Subscriptions.cs b/src/NServiceBus.Core.Tests/Unicast/Subscriptions.cs deleted file mode 100644 index 02f136e57fa..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Subscriptions.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using Core.Tests.Fakes; - using NUnit.Framework; - - [TestFixture] - class When_subscribing_to_messages : using_the_unicastBus - { - readonly Address addressToOwnerOfTestMessage = new Address("TestMessageOwner", "localhost"); - /// - /// Set Up - /// - [SetUp] - public new void SetUp() - { - router.RegisterMessageRoute(typeof(TestMessage), addressToOwnerOfTestMessage); - } - - [Test] - public void Should_send_the_assemblyQualified_name_as_subscription_type() - { - bus.Subscribe(); - - AssertSubscription(m => m.Headers.ContainsKey(Headers.SubscriptionMessageType) && - m.Headers[Headers.SubscriptionMessageType] == typeof(TestMessage).AssemblyQualifiedName, - addressToOwnerOfTestMessage); - - } - - [Test] - public void Should_set_the_message_intent_to_subscribe() - { - bus.Subscribe(); - - - AssertSubscription(m => m.MessageIntent == MessageIntentEnum.Subscribe && - m.Headers.ContainsKey(Headers.NServiceBusVersion) && - m.Headers.ContainsKey(Headers.TimeSent) - ,addressToOwnerOfTestMessage); - } - } - - [TestFixture] - class When_using_a_non_centralized_pub_sub_transport : using_the_unicastBus - { - [Test] - public void Should_throw_when_subscribing_to_a_message_that_has_no_configured_address() - { - Assert.Throws(() => bus.Subscribe()); - } - - [Test] - public void Should_throw_when_unsubscribing_to_a_message_that_has_no_configured_address() - { - Assert.Throws(() => bus.Unsubscribe()); - } - } - - [TestFixture] - class When_using_a_centralized_pub_sub_transport : using_the_unicastBus - { - [SetUp] - public new void SetUp() - { - transportDefinition = new FakeCentralizedPubSubTransportDefinition(); - } - - [Test] - public void Should_not_throw_when_subscribing_to_a_message_that_has_no_configured_address() - { - Assert.DoesNotThrow(() => bus.Subscribe()); - } - - [Test] - public void Should_not_throw_When_unsubscribing_to_a_message_that_has_no_configured_address() - { - Assert.DoesNotThrow(() => bus.Unsubscribe()); - } - } - - [TestFixture] - class When_subscribing_to_command_messages : using_the_unicastBus - { - [Test] - public void Should_get_an_error_messages() - { - RegisterMessageType(); - Assert.Throws(() => bus.Subscribe()); - } - } - - [TestFixture] - class When_unsubscribing_to_command_messages : using_the_unicastBus - { - [Test] - public void Should_get_an_error_messages() - { - RegisterMessageType(); - Assert.Throws(() => bus.Unsubscribe()); - } - } - - [TestFixture] - public class When_creating_message_types - { - [Test] - public void Should_parse_types() - { - var messageType = new Subscriptions.MessageType(typeof(TestMessage)); - - Assert.AreEqual(messageType.TypeName, typeof(TestMessage).FullName); - Assert.AreEqual(messageType.Version, typeof(TestMessage).Assembly.GetName().Version); - } - - [Test] - public void Should_parse_AssemblyQualifiedName() - { - var messageType = new Subscriptions.MessageType(typeof(TestMessage).AssemblyQualifiedName); - - Assert.AreEqual(messageType.TypeName, typeof(TestMessage).FullName); - Assert.AreEqual(messageType.Version, typeof(TestMessage).Assembly.GetName().Version); - } - - [Test] - public void Should_parse_version_strings() - { - var messageType = new Subscriptions.MessageType("TestMessage", "1.2.3.4"); - - Assert.AreEqual(messageType.TypeName, "TestMessage"); - Assert.AreEqual(messageType.Version, new Version(1, 2, 3, 4)); - } - - - class TestMessage - { - - } - } -} diff --git a/src/NServiceBus.Core.Tests/Unicast/ThroughputLimiterTests.cs b/src/NServiceBus.Core.Tests/Unicast/ThroughputLimiterTests.cs deleted file mode 100644 index 2c106afc6b4..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/ThroughputLimiterTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using System.Diagnostics; - using System.Threading; - using System.Threading.Tasks; - using NUnit.Framework; - using Transport; - - [TestFixture] - public class ThroughputLimiterTests - { - [Test, Timeout(3500)] - public void Can_StartAndStop_Multiple_Times_Without_Deadlocks() - { - var limiter = new ThroughputLimiter(); - var manualResetEventSlim = new ManualResetEventSlim(false); - Console.Out.WriteLine("Starting"); - - limiter.Start(5); - Task.Factory.StartNew(() => - { - while (!manualResetEventSlim.IsSet) - { - limiter.MessageProcessed(); - Thread.Sleep(10); - } - - }, TaskCreationOptions.LongRunning); - - Thread.Sleep(TimeSpan.FromSeconds(1)); - Console.Out.WriteLine("Stopping"); - limiter.Stop(); - Console.Out.WriteLine("Starting"); - limiter.Start(10); - Thread.Sleep(TimeSpan.FromSeconds(1)); - Console.Out.WriteLine("Stopping"); - limiter.Stop(); - Console.Out.WriteLine("Starting"); - limiter.Start(0); - Thread.Sleep(TimeSpan.FromSeconds(1)); - Console.Out.WriteLine("Stopping"); - limiter.Stop(); - - manualResetEventSlim.Set(); - } - - [Test,Explicit("Not stable")] - public void One_message_limit_and_two_messages_should_take_more_than_one_second() - { - var stopwatch = Stopwatch.StartNew(); - var limiter = new ThroughputLimiter(); - limiter.Start(1); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - var elapsedMilliseconds = stopwatch.ElapsedMilliseconds; - Debug.WriteLine("took {0}ms", elapsedMilliseconds); - limiter.Stop(); - Assert.IsTrue(elapsedMilliseconds > 1000,string.Format("Expected more than 1000ms but received {0}ms", elapsedMilliseconds)); - } - - [Test] - public void Two_message_limit_and_nine_messages_should_take_more_than_four_second() - { - var stopwatch = Stopwatch.StartNew(); - var limiter = new ThroughputLimiter(); - limiter.Start(2); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - limiter.MessageProcessed(); - var elapsedMilliseconds = stopwatch.ElapsedMilliseconds; - Debug.WriteLine("took {0}ms", elapsedMilliseconds); - limiter.Stop(); - Assert.IsTrue(elapsedMilliseconds > 4000,string.Format("Expected more than 4000ms but received {0}ms", elapsedMilliseconds)); - } - - [Test] - public void Two_message_limit_and_one_messages_should_take_less_than_one_second() - { - var stopwatch = Stopwatch.StartNew(); - var limiter = new ThroughputLimiter(); - limiter.Start(2); - limiter.MessageProcessed(); - var elapsedMilliseconds = stopwatch.ElapsedMilliseconds; - Debug.WriteLine("took {0}ms", elapsedMilliseconds); - limiter.Stop(); - Assert.IsTrue(elapsedMilliseconds < 1000,string.Format("Expected less than 1000ms but received {0}ms", elapsedMilliseconds)); - } - - [Test] - public void One_message_limit_and_one_messages_should_take_less_than_one_second() - { - var stopwatch = Stopwatch.StartNew(); - var limiter = new ThroughputLimiter(); - limiter.Start(1); - limiter.MessageProcessed(); - var elapsedMilliseconds = stopwatch.ElapsedMilliseconds; - Debug.WriteLine("took {0}ms", elapsedMilliseconds); - limiter.Stop(); - Assert.IsTrue(elapsedMilliseconds < 1000,string.Format("Expected less than 1000ms but received {0}ms", elapsedMilliseconds)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/Timing.cs b/src/NServiceBus.Core.Tests/Unicast/Timing.cs deleted file mode 100644 index 7469edd614d..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/Timing.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using Contexts; - using NUnit.Framework; - using Rhino.Mocks; - - - [TestFixture] - class When_sending_a_message_with_timing_turned_on : using_the_unicastBus - { - [Test] - public void Should_set_the_time_sent_header() - { - RegisterMessageType(); - - bus.Send(new CommandMessage()); - messageSender.AssertWasCalled(x => x.Send(Arg.Matches(m => m.Headers.ContainsKey("NServiceBus.TimeSent")), Arg.Is.Anything)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/UnicastBusAuthorizationTests.cs b/src/NServiceBus.Core.Tests/Unicast/UnicastBusAuthorizationTests.cs deleted file mode 100644 index 1fc325bbe7d..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/UnicastBusAuthorizationTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using System.Collections.Generic; - using NUnit.Framework; - - [TestFixture] - public class UnicastBusAuthorizationTests - { - - [Test] - public void Should_use_noop_for_no_authorizer() - { - var authorizationType = Features.UnicastBus.FindAuthorizationType(new List()); - Assert.AreEqual("NoopSubscriptionAuthorizer", authorizationType.Name); - } - - [Test] - public void Should_use_single_for_one_authorizer() - { - var authorizationType = Features.UnicastBus.FindAuthorizationType(new List() - { - typeof(Authorizer1) - }); - Assert.AreEqual(typeof(Authorizer1), authorizationType); - } - - [Test] - public void Should_throw_for_multiple_authorizer() - { - var exception = Assert.Throws(() => Features.UnicastBus.FindAuthorizationType(new List() - { - typeof(Authorizer1), - typeof(Authorizer2) - })); - Assert.AreEqual("Only one instance of IAuthorizeSubscriptions is allowed. Found the following: 'NServiceBus.Unicast.Tests.UnicastBusAuthorizationTests+Authorizer1', 'NServiceBus.Unicast.Tests.UnicastBusAuthorizationTests+Authorizer2'.", exception.Message); - } - - class Authorizer1 : IAuthorizeSubscriptions - { - public bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - throw new NotImplementedException(); - } - - public bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - throw new NotImplementedException(); - } - } - - class Authorizer2 : IAuthorizeSubscriptions - { - public bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - throw new NotImplementedException(); - } - - public bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - throw new NotImplementedException(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/UnitOfWork.cs b/src/NServiceBus.Core.Tests/Unicast/UnitOfWork.cs deleted file mode 100644 index 1e7eff81787..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/UnitOfWork.cs +++ /dev/null @@ -1,326 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Contexts; - using NUnit.Framework; - using UnitOfWork; - - [TestFixture] - class When_processing_a_subscribe_message_successfully : using_the_unicastBus - { - [Test] - public void Should_invoke_the_uow_begin_and_end() - { - var beginCalled = false; - var endCalled = false; - - var uow = new TestUnitOfWork - { - OnBegin = () => - { - beginCalled = true; - Assert.False(endCalled); - }, - OnEnd = ex => { Assert.Null(ex); endCalled = true; } - }; - - RegisterUow(uow); - ReceiveMessage(Helpers.Helpers.EmptySubscriptionMessage()); - - Assert.True(beginCalled); - Assert.True(endCalled); - } - } - - [TestFixture] - class When_begin_and_end_executes : using_the_unicastBus - { - [Test] - public void Should_invoke_ends_in_reverse_order() - { - var currentEnd = 0; - int firstUoWEndOrder = 0, secondUoWEndOrder = 0, thirdUoWEndOrder = 0; - var firstUoW = new TestUnitOfWork - { - ExpectedEndOrder = 3, - OnEnd = ex => { firstUoWEndOrder = ++currentEnd; } - }; - var secondUoW = new TestUnitOfWork - { - ExpectedEndOrder = 2, - OnEnd = ex => { secondUoWEndOrder = ++currentEnd; } - }; - var thirdUoW = new TestUnitOfWork - { - ExpectedEndOrder = 1, - OnEnd = ex => { thirdUoWEndOrder = ++currentEnd; } - }; - RegisterUow(firstUoW); - RegisterUow(secondUoW); - RegisterUow(thirdUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.AreEqual(firstUoW.ExpectedEndOrder, firstUoWEndOrder); - Assert.AreEqual(secondUoW.ExpectedEndOrder, secondUoWEndOrder); - Assert.AreEqual(thirdUoW.ExpectedEndOrder, thirdUoWEndOrder); - } - - [Test] - public void Should_invoke_ends_in_reverse_order_even_if_an_end_throws() - { - var currentEnd = 0; - int firstUoWEndOrder = 0, secondUoWEndOrder = 0, thirdUoWEndOrder = 0; - var firstUoW = new TestUnitOfWork - { - ExpectedEndOrder = 3, - OnEnd = ex => { firstUoWEndOrder = ++currentEnd; } - }; - var secondUoW = new TestUnitOfWork - { - ExpectedEndOrder = 2, - OnEnd = ex => - { - secondUoWEndOrder = ++currentEnd; - throw new Exception(); - } - }; - var thirdUoW = new TestUnitOfWork - { - ExpectedEndOrder = 1, - OnEnd = ex => { thirdUoWEndOrder = ++currentEnd; } - }; - RegisterUow(firstUoW); - RegisterUow(secondUoW); - RegisterUow(thirdUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - Assert.AreEqual(firstUoW.ExpectedEndOrder, firstUoWEndOrder); - Assert.AreEqual(secondUoW.ExpectedEndOrder, secondUoWEndOrder); - Assert.AreEqual(thirdUoW.ExpectedEndOrder, thirdUoWEndOrder); - } - - [Test] - public void Should_invoke_ends_in_reverse_order_even_if_a_begin_throws() - { - var currentEnd = 0; - int firstUoWEndOrder = 0, secondUoWEndOrder = 0, thirdUoWEndOrder = -1; - var firstUoW = new TestUnitOfWork - { - ExpectedEndOrder = 2, - OnEnd = ex => { firstUoWEndOrder = ++currentEnd; } - }; - var secondUoW = new TestUnitOfWork - { - ExpectedEndOrder = 1, - OnBegin = () => - { - throw new Exception(); - }, - OnEnd = ex => - { - secondUoWEndOrder = ++currentEnd; - } - }; - var thirdUoW = new TestUnitOfWork - { - ExpectedEndOrder = -1, - OnEnd = ex => { thirdUoWEndOrder = ++currentEnd; } - }; - RegisterUow(firstUoW); - RegisterUow(secondUoW); - RegisterUow(thirdUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.AreEqual(firstUoW.ExpectedEndOrder, firstUoWEndOrder); - Assert.AreEqual(secondUoW.ExpectedEndOrder, secondUoWEndOrder); - Assert.AreEqual(thirdUoW.ExpectedEndOrder, thirdUoWEndOrder); - } - } - - [TestFixture] - class When_a_uow_end_throws : using_the_unicastBus - { - [Test] - public void Should_invoke_end_if_begin_was_invoked() - { - var firstEndCalled = false; - var throwableEndCalled = false; - var lastEndCalled = false; - - var firstUoW = new TestUnitOfWork - { - OnEnd = ex => { firstEndCalled = true; } - }; - var throwableUoW = new TestUnitOfWork - { - OnEnd = ex => - { - throwableEndCalled = true; - throw new Exception(); - } - }; - var lastUoW = new TestUnitOfWork - { - OnEnd = ex => { lastEndCalled = true; } - }; - RegisterUow(firstUoW); - RegisterUow(throwableUoW); - RegisterUow(lastUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.True(firstEndCalled); - Assert.True(throwableEndCalled); - Assert.True(lastEndCalled); - } - - [Test] - public void Should_invoke_each_end_only_once() - { - var firstEndCalled = 0; - var throwableEndCalled = 0; - var lastEndCalled = 0; - - var firstUoW = new TestUnitOfWork - { - OnEnd = ex => { firstEndCalled++; } - }; - var throwableUoW = new TestUnitOfWork - { - OnEnd = ex => - { - throwableEndCalled++; - throw new Exception(); - } - }; - var lastUoW = new TestUnitOfWork - { - OnEnd = ex => { lastEndCalled++; } - }; - RegisterUow(firstUoW); - RegisterUow(throwableUoW); - RegisterUow(lastUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.AreEqual(1, firstEndCalled); - Assert.AreEqual(1, throwableEndCalled); - Assert.AreEqual(1, lastEndCalled); - } - } - - [TestFixture] - class When_a_uow_begin_throws : using_the_unicastBus - { - [Test] - public void Should_not_invoke_end_if_begin_was_not_invoked() - { - var firstEndCalled = false; - var throwableEndCalled = false; - var lastEndCalled = false; - - var firstUoW = new TestUnitOfWork - { - OnEnd = ex => { firstEndCalled = true; } - }; - var throwableUoW = new TestUnitOfWork - { - OnBegin = () => - { - throw new Exception(); - }, - OnEnd = ex => { throwableEndCalled = true; } - }; - var lastUoW = new TestUnitOfWork - { - OnEnd = ex => { lastEndCalled = true; } - }; - RegisterUow(firstUoW); - RegisterUow(throwableUoW); - RegisterUow(lastUoW); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.True(firstEndCalled); - Assert.True(throwableEndCalled); - Assert.False(lastEndCalled); - } - } - - [TestFixture] - class When_processing_a_message_successfully : using_the_unicastBus - { - [Test] - public void Should_invoke_the_uow_begin_and_end() - { - var beginCalled = false; - var endCalled = false; - - var uow = new TestUnitOfWork - { - OnBegin = () => - { - beginCalled = true; - Assert.False(endCalled); - }, - OnEnd = ex => { Assert.Null(ex); endCalled = true; } - }; - - RegisterUow(uow); - ReceiveMessage(Helpers.Helpers.EmptyTransportMessage()); - - Assert.True(beginCalled); - Assert.True(endCalled); - } - } - - [TestFixture] - class When_processing_a_message_fails : using_the_unicastBus - { - [Test] - public void Should_pass_the_exception_to_the_uow_end() - { - RegisterMessageType(); - - handlerRegistry.RegisterHandler(typeof(MessageThatBlowsUpHandler)); - - var endCalled = false; - - var uow = new TestUnitOfWork - { - OnEnd = ex => { Assert.NotNull(ex); endCalled = true; } - }; - - RegisterUow(uow); - ReceiveMessage(Helpers.Helpers.Serialize(new MessageThatBlowsUp())); - - Assert.True(endCalled); - } - } - - public class MessageThatBlowsUpHandler:IHandleMessages - { - public void Handle(MessageThatBlowsUp message) - { - throw new Exception("Generated failure"); - } - } - - public class MessageThatBlowsUp:IMessage - { - } - - public class TestUnitOfWork : IManageUnitsOfWork - { - public int ExpectedEndOrder; - - public Action OnEnd = ex => { }; - public Action OnBegin = () => { }; - - public void Begin() - { - OnBegin(); - } - - public void End(Exception ex = null) - { - OnEnd(ex); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Unicast/UnitOfWorkBehaviorTests.cs b/src/NServiceBus.Core.Tests/Unicast/UnitOfWorkBehaviorTests.cs deleted file mode 100644 index fbf358cb7ba..00000000000 --- a/src/NServiceBus.Core.Tests/Unicast/UnitOfWorkBehaviorTests.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace NServiceBus.Unicast.Tests -{ - using System; - using Core.Tests; - using NUnit.Framework; - using ObjectBuilder; - using Pipeline.Contexts; - using UnitOfWork; - - [TestFixture] - public class UnitOfWorkBehaviorTests - { - [Test] - public void When_first_throw_second_is_cleaned_up() - { - var builder = new FuncBuilder(); - - var unitOfWorkThatThrowsFromEnd = new UnitOfWorkThatThrowsFromEnd(); - var unitOfWork = new UnitOfWork(); - - builder.Register(() => unitOfWorkThatThrowsFromEnd); - builder.Register(() => unitOfWork); - - //since it is a single exception then it will not be an AggregateException - Assert.Throws(() => InvokeBehavior(builder)); - Assert.IsTrue(unitOfWorkThatThrowsFromEnd.BeginCalled); - Assert.IsTrue(unitOfWorkThatThrowsFromEnd.EndCalled); - Assert.IsTrue(unitOfWork.BeginCalled); - Assert.IsTrue(unitOfWork.EndCalled); - } - - [Test] - public void Should_append_end_exception_to_rethrow() - { - var builder = new FuncBuilder(); - - var unitOfWork = new UnitOfWorkThatThrowsFromEnd(); - - builder.Register(() => unitOfWork); - //since it is a single exception then it will not be an AggregateException - var exception = Assert.Throws(() => InvokeBehavior(builder)); - - Assert.AreSame(unitOfWork.ExceptionThrownFromEnd, exception); - } - - public void InvokeBehavior(IBuilder builder) - { - var runner = new UnitOfWorkBehavior(); - - var context = new IncomingContext(new RootContext(builder), new TransportMessage()); - - runner.Invoke(context, () => { }); - - } - - public class UnitOfWorkThatThrowsFromEnd : IManageUnitsOfWork - { - public bool BeginCalled; - public bool EndCalled; - public Exception ExceptionThrownFromEnd = new InvalidOperationException(); - - public void Begin() - { - BeginCalled = true; - } - - public void End(Exception ex = null) - { - EndCalled = true; - throw ExceptionThrownFromEnd; - } - - } - - public class UnitOfWork : IManageUnitsOfWork - { - public bool BeginCalled; - public bool EndCalled; - - public void Begin() - { - BeginCalled = true; - } - - public void End(Exception ex = null) - { - EndCalled = true; - } - } - - [Test] - public void Verify_order() - { - var builder = new FuncBuilder(); - - var unitOfWork1 = new CountingUnitOfWork(); - var unitOfWork2 = new CountingUnitOfWork(); - var unitOfWork3 = new CountingUnitOfWork(); - - builder.Register(() => unitOfWork1); - builder.Register(() => unitOfWork2); - builder.Register(() => unitOfWork3); - - InvokeBehavior(builder); - - Assert.AreEqual(1, unitOfWork1.BeginCallIndex); - Assert.AreEqual(2, unitOfWork2.BeginCallIndex); - Assert.AreEqual(3, unitOfWork3.BeginCallIndex); - Assert.AreEqual(3, unitOfWork1.EndCallIndex); - Assert.AreEqual(2, unitOfWork2.EndCallIndex); - Assert.AreEqual(1, unitOfWork3.EndCallIndex); - } - - public class CountingUnitOfWork : IManageUnitsOfWork - { - static int BeginCallCount; - static int EndCallCount; - public int EndCallIndex; - public int BeginCallIndex; - - public void Begin() - { - BeginCallCount++; - BeginCallIndex = BeginCallCount; - } - public void End(Exception ex = null) - { - EndCallCount++; - EndCallIndex = EndCallCount; - } - } - - [Test] - public void Should_pass_exception_to_cleanup() - { - var builder = new FuncBuilder(); - - var unitOfWork = new CaptureExceptionPassedToEndUnitOfWork(); - var throwingUoW = new UnitOfWorkThatThrowsFromEnd(); - - builder.Register(() => unitOfWork); - builder.Register(() => throwingUoW); - - //since it is a single exception then it will not be an AggregateException - var exception = Assert.Throws(() => InvokeBehavior(builder)); - - Assert.AreSame(throwingUoW.ExceptionThrownFromEnd, unitOfWork.Exception); - Assert.AreSame(throwingUoW.ExceptionThrownFromEnd, exception); - } - - public class CaptureExceptionPassedToEndUnitOfWork : IManageUnitsOfWork - { - public void Begin() - { - } - public void End(Exception ex = null) - { - Exception = ex; - } - public Exception Exception; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/UnitOfWork/UnitOfWorkBehaviorTests.cs b/src/NServiceBus.Core.Tests/UnitOfWork/UnitOfWorkBehaviorTests.cs new file mode 100644 index 00000000000..8aeb8fae27b --- /dev/null +++ b/src/NServiceBus.Core.Tests/UnitOfWork/UnitOfWorkBehaviorTests.cs @@ -0,0 +1,341 @@ +namespace NServiceBus.Unicast.Tests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Testing; + using UnitOfWork; + + [TestFixture] + public class UnitOfWorkBehaviorTests + { + [Test] + public void When_first_throw_second_is_cleaned_up() + { + var builder = new FakeBuilder(); + + var unitOfWorkThatThrowsFromEnd = new UnitOfWorkThatThrowsFromEnd(); + var unitOfWork = new UnitOfWork(); + + builder.Register(unitOfWorkThatThrowsFromEnd, unitOfWork); + + //since it is a single exception then it will not be an AggregateException + Assert.That(async () => await InvokeBehavior(builder), Throws.InvalidOperationException); + Assert.IsTrue(unitOfWorkThatThrowsFromEnd.BeginCalled); + Assert.IsTrue(unitOfWorkThatThrowsFromEnd.EndCalled); + Assert.IsTrue(unitOfWork.BeginCalled); + Assert.IsTrue(unitOfWork.EndCalled); + } + + [Test] + public void Should_append_end_exception_to_rethrow() + { + var builder = new FakeBuilder(); + + var unitOfWork = new UnitOfWorkThatThrowsFromEnd(); + + builder.Register(() => unitOfWork); + + //since it is a single exception then it will not be an AggregateException + Assert.That(async () => await InvokeBehavior(builder), Throws.InvalidOperationException.And.SameAs(unitOfWork.ExceptionThrownFromEnd)); + } + + [Test] + public void Should_not_invoke_end_if_begin_was_not_invoked() + { + var builder = new FakeBuilder(); + + var unitOfWorkThatThrowsFromBegin = new UnitOfWorkThatThrowsFromBegin(); + var unitOfWork = new UnitOfWork(); + + builder.Register(unitOfWorkThatThrowsFromBegin, unitOfWork); + + //since it is a single exception then it will not be an AggregateException + Assert.That(async () => await InvokeBehavior(builder), Throws.InvalidOperationException); + Assert.False(unitOfWork.EndCalled); + + } + + [Test] + public void Should_pass_exceptions_to_the_uow_end() + { + var builder = new FakeBuilder(); + + var unitOfWork = new UnitOfWork(); + + builder.Register(() => unitOfWork); + + var ex = new Exception("Handler failed"); + //since it is a single exception then it will not be an AggregateException + Assert.That(async () => await InvokeBehavior(builder, ex), Throws.InstanceOf().And.SameAs(ex)); + Assert.AreSame(ex, unitOfWork.ExceptionPassedToEnd); + } + + [Test] + public async Task Should_invoke_ends_in_reverse_order_of_the_begins() + { + var builder = new FakeBuilder(); + + var order = new List(); + var firstUnitOfWork = new OrderAwareUnitOfWork("first", order); + var secondUnitOfWork = new OrderAwareUnitOfWork("second", order); + + + builder.Register(firstUnitOfWork, secondUnitOfWork); + + await InvokeBehavior(builder); + + Assert.AreEqual("first", order[0]); + Assert.AreEqual("second", order[1]); + Assert.AreEqual("second", order[2]); + Assert.AreEqual("first", order[3]); + } + + [Test] + public void Should_call_all_end_even_if_one_or_more_of_them_throws() + { + var builder = new FakeBuilder(); + + var unitOfWorkThatThrows = new UnitOfWorkThatThrowsFromEnd(); + var unitOfWork = new UnitOfWork(); + + builder.Register(unitOfWorkThatThrows, unitOfWork); + + Assert.That(async () => await InvokeBehavior(builder), Throws.InvalidOperationException); + Assert.True(unitOfWork.EndCalled); + } + + [Test] + public void Should_invoke_ends_on_all_begins_that_was_called_even_when_begin_throws() + { + var builder = new FakeBuilder(); + + var normalUnitOfWork = new UnitOfWork(); + var unitOfWorkThatThrows = new UnitOfWorkThatThrowsFromBegin(); + var unitOfWorkThatIsNeverCalled = new UnitOfWork(); + + builder.Register(normalUnitOfWork, unitOfWorkThatThrows, unitOfWorkThatIsNeverCalled); + + Assert.That(async () => await InvokeBehavior(builder), Throws.InvalidOperationException); + + Assert.True(normalUnitOfWork.EndCalled); + Assert.True(unitOfWorkThatThrows.EndCalled); + Assert.False(unitOfWorkThatIsNeverCalled.EndCalled); + } + + [Test] + public void Should_throw_friendly_exception_if_IManageUnitsOfWork_Begin_returns_null() + { + var builder = new FakeBuilder(); + + builder.Register(() => new UnitOfWorkThatReturnsNullForBegin()); + Assert.That(async () => await InvokeBehavior(builder), + Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + [Test] + public void Should_throw_friendly_exception_if_IManageUnitsOfWork_End_returns_null() + { + var builder = new FakeBuilder(); + + builder.Register(() => new UnitOfWorkThatReturnsNullForEnd()); + Assert.That(async () => await InvokeBehavior(builder), + Throws.Exception.With.Message.EqualTo("Return a Task or mark the method as async.")); + } + + static Task InvokeBehavior(FakeBuilder builder, Exception toThrow = null) + { + var runner = new UnitOfWorkBehavior(); + + var context = new TestableIncomingPhysicalMessageContext(); + context.Builder = builder; + + return runner.Invoke(context, ctx => + { + if (toThrow != null) + { + throw toThrow; + } + return TaskEx.CompletedTask; + }); + } + + class UnitOfWorkThatThrowsFromEnd : IManageUnitsOfWork + { + public bool BeginCalled; + public bool EndCalled; + public Exception ExceptionThrownFromEnd = new InvalidOperationException(); + + public Task Begin() + { + BeginCalled = true; + return TaskEx.CompletedTask; + } + + public Task End(Exception ex = null) + { + EndCalled = true; + throw ExceptionThrownFromEnd; + } + + } + + class UnitOfWorkThatThrowsFromBegin : IManageUnitsOfWork + { + public bool EndCalled; + public Exception ExceptionThrownFromEnd = new InvalidOperationException(); + + public Task Begin() + { + throw ExceptionThrownFromEnd; + } + + public Task End(Exception ex = null) + { + EndCalled = true; + return TaskEx.CompletedTask; + } + } + + class UnitOfWork : IManageUnitsOfWork + { + public bool BeginCalled; + public bool EndCalled; + public Exception ExceptionPassedToEnd; + public Task Begin() + { + BeginCalled = true; + return TaskEx.CompletedTask; + } + + public Task End(Exception ex = null) + { + ExceptionPassedToEnd = ex; + EndCalled = true; + return TaskEx.CompletedTask; + } + } + + class UnitOfWorkThatReturnsNullForBegin : IManageUnitsOfWork + { + public Task Begin() + { + return null; + } + + public Task End(Exception ex = null) + { + return TaskEx.CompletedTask; + } + } + + class UnitOfWorkThatReturnsNullForEnd : IManageUnitsOfWork + { + public Task Begin() + { + return TaskEx.CompletedTask; + } + + public Task End(Exception ex = null) + { + return null; + } + } + + [Test] + public async Task Verify_order() + { + var builder = new FakeBuilder(); + + var unitOfWork1 = new CountingUnitOfWork(); + var unitOfWork2 = new CountingUnitOfWork(); + var unitOfWork3 = new CountingUnitOfWork(); + + builder.Register(unitOfWork1, unitOfWork2, unitOfWork3); + + await InvokeBehavior(builder); + + Assert.AreEqual(1, unitOfWork1.BeginCallIndex); + Assert.AreEqual(2, unitOfWork2.BeginCallIndex); + Assert.AreEqual(3, unitOfWork3.BeginCallIndex); + Assert.AreEqual(3, unitOfWork1.EndCallIndex); + Assert.AreEqual(2, unitOfWork2.EndCallIndex); + Assert.AreEqual(1, unitOfWork3.EndCallIndex); + } + + class CountingUnitOfWork : IManageUnitsOfWork + { + static int BeginCallCount; + static int EndCallCount; + public int EndCallIndex; + public int BeginCallIndex; + + public Task Begin() + { + BeginCallCount++; + BeginCallIndex = BeginCallCount; + return TaskEx.CompletedTask; + } + public Task End(Exception ex = null) + { + EndCallCount++; + EndCallIndex = EndCallCount; + return TaskEx.CompletedTask; + } + } + + [Test] + public void Should_pass_exception_to_cleanup() + { + var builder = new FakeBuilder(); + + var unitOfWork = new CaptureExceptionPassedToEndUnitOfWork(); + var throwingUoW = new UnitOfWorkThatThrowsFromEnd(); + + builder.Register(unitOfWork, throwingUoW); + + //since it is a single exception then it will not be an AggregateException + Assert.That(async () => await InvokeBehavior(builder), Throws.InstanceOf().And.SameAs(throwingUoW.ExceptionThrownFromEnd)); + Assert.AreSame(throwingUoW.ExceptionThrownFromEnd, unitOfWork.Exception); + } + + class CaptureExceptionPassedToEndUnitOfWork : IManageUnitsOfWork + { + public Task Begin() + { + return TaskEx.CompletedTask; + } + public Task End(Exception ex = null) + { + Exception = ex; + return TaskEx.CompletedTask; + } + public Exception Exception; + } + + class OrderAwareUnitOfWork : IManageUnitsOfWork + { + string name; + List order; + + public OrderAwareUnitOfWork(string name, List order) + { + this.name = name; + this.order = order; + } + + public Task Begin() + { + order.Add(name); + return TaskEx.CompletedTask; + } + + public Task End(Exception ex = null) + { + order.Add(name); + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Utils/ExceptionHeaderHelperTests.cs b/src/NServiceBus.Core.Tests/Utils/ExceptionHeaderHelperTests.cs index 56912beee48..538cbf02f15 100644 --- a/src/NServiceBus.Core.Tests/Utils/ExceptionHeaderHelperTests.cs +++ b/src/NServiceBus.Core.Tests/Utils/ExceptionHeaderHelperTests.cs @@ -1,10 +1,8 @@ namespace NServiceBus.Core.Tests.Utils { using System; - using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; - using NServiceBus.Faults; using NUnit.Framework; [TestFixture] @@ -16,36 +14,10 @@ public void VerifyHeadersAreSet() var exception = GetAnException(); var dictionary = new Dictionary(); - var failedQueue = new Address("TheErrorQueue", "TheErrorQueueMachine"); - ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception, failedQueue, "The reason", false); - Assert.AreEqual("The reason", dictionary["NServiceBus.ExceptionInfo.Reason"]); - Assert.AreEqual("System.AggregateException", dictionary["NServiceBus.ExceptionInfo.ExceptionType"]); - var stackTrace = dictionary["NServiceBus.ExceptionInfo.StackTrace"]; - Assert.IsTrue(stackTrace.StartsWith(@"System.AggregateException: My Exception ---> System.Exception: My Inner Exception - at NServiceBus.Core.Tests.Utils.ExceptionHeaderHelperTests.MethodThatThrows2() in ")); - Assert.AreEqual("TheErrorQueue@TheErrorQueueMachine", dictionary[FaultsHeaderKeys.FailedQ]); - Assert.IsTrue(dictionary.ContainsKey("NServiceBus.TimeOfFailure")); - - Assert.AreEqual("System.Exception", dictionary["NServiceBus.ExceptionInfo.InnerExceptionType"]); - Assert.AreEqual("A fake help link", dictionary["NServiceBus.ExceptionInfo.HelpLink"]); - Assert.AreEqual("NServiceBus.Core.Tests", dictionary["NServiceBus.ExceptionInfo.Source"]); - } - - + ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception); - [Test] - public void VerifyLegacyHeadersAreSet() - { - var exception = GetAnException(); - var dictionary = new Dictionary(); - - var failedQueue = new Address("TheErrorQueue", "TheErrorQueueMachine"); - ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception, failedQueue, "The reason", true); - Assert.AreEqual("The reason", dictionary["NServiceBus.ExceptionInfo.Reason"]); Assert.AreEqual("System.AggregateException", dictionary["NServiceBus.ExceptionInfo.ExceptionType"]); - var stackTrace = dictionary["NServiceBus.ExceptionInfo.StackTrace"]; - Assert.IsTrue(stackTrace.StartsWith(@" at NServiceBus.Core.Tests.Utils.ExceptionHeaderHelperTests.MethodThatThrows1() in ")); - Assert.AreEqual("TheErrorQueue@TheErrorQueueMachine", dictionary[FaultsHeaderKeys.FailedQ]); + Assert.AreEqual(exception.ToString(), dictionary["NServiceBus.ExceptionInfo.StackTrace"]); Assert.IsTrue(dictionary.ContainsKey("NServiceBus.TimeOfFailure")); Assert.AreEqual("System.Exception", dictionary["NServiceBus.ExceptionInfo.InnerExceptionType"]); @@ -85,46 +57,13 @@ void MethodThatThrows2() throw new Exception("My Inner Exception"); } - [Test] - public void VerifyDataIsSet() - { - var exception = GetAnException(); - exception.Data["TestKey"] = "MyValue"; - - var dictionary = new Dictionary(); - - var failedQueue = new Address("TheErrorQueue", "TheErrorQueueMachine"); - ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception, failedQueue, "The reason", false); - - Assert.AreEqual("MyValue", dictionary["NServiceBus.ExceptionInfo.Data.TestKey"]); - } - - class NullDataException : Exception - { - public override IDictionary Data - { -// ReSharper disable once AssignNullToNotNullAttribute - get { return null; } - } - } - - [Test] - public void VerifyNullDataDoesNotThrow() - { - var exception = new NullDataException(); - var dictionary = new Dictionary(); - - var failedQueue = new Address("TheErrorQueue", "TheErrorQueueMachine"); - ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception, failedQueue, "The reason", false); - } - [Test] public void ExceptionMessageIsTruncated() { var exception = new Exception(new string('x', (int)Math.Pow(2, 15))); var dictionary = new Dictionary(); - ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception, new Address("queue1", "machine1"), "reason1", false); + ExceptionHeaderHelper.SetExceptionHeaders(dictionary, exception); Assert.AreEqual((int)Math.Pow(2, 14), dictionary["NServiceBus.ExceptionInfo.Message"].Length); } diff --git a/src/NServiceBus.Core.Tests/Utils/Reflection/ExtensionMethodsTests.cs b/src/NServiceBus.Core.Tests/Utils/Reflection/ExtensionMethodsTests.cs index d4a11cb4626..6b996bcea5b 100644 --- a/src/NServiceBus.Core.Tests/Utils/Reflection/ExtensionMethodsTests.cs +++ b/src/NServiceBus.Core.Tests/Utils/Reflection/ExtensionMethodsTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; - using NServiceBus.Utils.Reflection; using NUnit.Framework; [TestFixture] @@ -45,7 +44,7 @@ public void Should_return_false_for_SN_and_non_particular_assembly() [Test] public void Should_return_true_for_particular_assembly() { - Assert.IsTrue(typeof(ExecuteLogicalMessagesBehavior).IsFromParticularAssembly()); + Assert.IsTrue(typeof(TransportReceiveToPhysicalMessageProcessingConnector).IsFromParticularAssembly()); } [Test] diff --git a/src/NServiceBus.Core.Tests/Utils/Reflection/ReflectTests.cs b/src/NServiceBus.Core.Tests/Utils/Reflection/ReflectTests.cs index 15ab988147f..f27d63ce380 100644 --- a/src/NServiceBus.Core.Tests/Utils/Reflection/ReflectTests.cs +++ b/src/NServiceBus.Core.Tests/Utils/Reflection/ReflectTests.cs @@ -1,7 +1,6 @@ namespace NServiceBus.Core.Utils.Reflection { using System; - using NServiceBus.Utils.Reflection; using NUnit.Framework; [TestFixture] @@ -51,11 +50,13 @@ public void Should_return_property_name() var propertyInfo = Reflect.GetProperty(target => target.Property1.Property2); Assert.AreEqual("Property2", propertyInfo.Name); } + [Test] public void Should_throw_when_dots_not_allowed() { var argumentException = Assert.Throws(() => Reflect.GetProperty(target => target.Property1.Property2, true)); - Assert.AreEqual("Argument passed contains more than a single dot which is not allowed: target => target.Property1.Property2\r\nParameter name: member", argumentException.Message); + StringAssert.StartsWith("Argument passed contains more than a single dot which is not allowed: target => target.Property1.Property2", argumentException.Message); + Assert.AreEqual("member", argumentException.ParamName); } public class Target1 diff --git a/src/NServiceBus.Core.Tests/packages.config b/src/NServiceBus.Core.Tests/packages.config index 5d8ca942741..d1b31763bd4 100644 --- a/src/NServiceBus.Core.Tests/packages.config +++ b/src/NServiceBus.Core.Tests/packages.config @@ -1,12 +1,9 @@  - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.Core/Address.cs b/src/NServiceBus.Core/Address.cs deleted file mode 100644 index 8cc3fac7b62..00000000000 --- a/src/NServiceBus.Core/Address.cs +++ /dev/null @@ -1,263 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Net; - using System.Runtime.Serialization; - using System.Security; - using Support; - - /// - /// Abstraction for an address on the NServiceBus network. - /// - [Serializable] - public class Address : ISerializable - { - /// - /// Undefined address. - /// - public static readonly Address Undefined = new Address(String.Empty, String.Empty); - - /// - /// Self address. - /// - public static readonly Address Self = new Address("__self", "localhost"); - - /// - /// Get the address of this endpoint. - /// - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Please inject an instance of `Configure` and call `Configure.LocalAddress` instead")] - public static Address Local - { - get { throw new InvalidOperationException(); } - } - - /// - /// Sets the address of this endpoint. - /// - /// The queue name. - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Replacement = "ConfigureTransport.LocalAddress(queue)")] -// ReSharper disable once UnusedParameter.Global - public static void InitializeLocalAddress(string queue) - { - throw new InvalidOperationException(); - } - - /// - /// Sets the public return address of this endpoint. - /// - /// The public address. - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Use `configuration.OverridePublicReturnAddress(address)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] -// ReSharper disable once UnusedParameter.Global - public static void OverridePublicReturnAddress(Address address) - { - throw new InvalidOperationException(); - } - - /// - /// Sets the address mode, can only be done as long as the local address is not been initialized.By default the default machine equals Environment.MachineName - /// - /// The machine name. - public static void OverrideDefaultMachine(string machineName) - { - defaultMachine = machineName; - } - - /// - /// Instructed the address to not consider the machine name - /// - public static void IgnoreMachineName() - { - ignoreMachineName = true; - } - - /// - /// Parses a string and returns an Address. - /// - /// The full address to parse. - /// A new instance of . - public static Address Parse(string destination) - { - if (string.IsNullOrEmpty(destination)) - { - throw new ArgumentException("Invalid destination address specified", "destination"); - } - - var arr = destination.Split('@'); - - var queue = arr[0]; - var machine = defaultMachine; - - if (String.IsNullOrWhiteSpace(queue)) - { - throw new ArgumentException("Invalid destination address specified", "destination"); - } - - if (arr.Length == 2) - if (arr[1] != "." && arr[1].ToLower() != "localhost" && arr[1] != IPAddress.Loopback.ToString()) - machine = arr[1]; - - return new Address(queue, machine); - } - - /// - /// Instantiate a new Address for a known queue on a given machine. - /// - ///The queue name. - ///The machine name. - public Address(string queueName, string machineName) - { - Queue = queueName; - queueLowerCased = queueName.ToLower(); - Machine = machineName ?? defaultMachine; - machineLowerCased = Machine.ToLower(); - } - - /// - /// Deserializes an Address. - /// - /// The to populate with data. - /// The destination (see ) for this serialization. - protected Address(SerializationInfo info, StreamingContext context) - { - Queue = info.GetString("Queue"); - Machine = info.GetString("Machine"); - - if (!String.IsNullOrEmpty(Queue)) - { - queueLowerCased = Queue.ToLower(); - } - - if (!String.IsNullOrEmpty(Machine)) - { - machineLowerCased = Machine.ToLower(); - } - } - - /// - /// Populates a with the data needed to serialize the target object. - /// - /// The to populate with data. - /// The destination (see ) for this serialization. - /// The caller does not have the required permission. - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Queue", Queue); - info.AddValue("Machine", Machine); - } - - /// - /// Creates a new Address whose Queue is derived from the Queue of the existing Address - /// together with the provided qualifier. For example: queue.qualifier@machine - /// - public Address SubScope(string qualifier) - { - return new Address(Queue + "." + qualifier, Machine); - } - - /// - /// Provides a hash code of the Address. - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = ((queueLowerCased != null ? queueLowerCased.GetHashCode() : 0) * 397); - - if (!ignoreMachineName) - { - hashCode ^= (machineLowerCased != null ? machineLowerCased.GetHashCode() : 0); - } - return hashCode; - } - } - - /// - /// Returns a string representation of the address. - /// - public override string ToString() - { - if (ignoreMachineName) - return Queue; - - return Queue + "@" + Machine; - } - - /// - /// The (lowercase) name of the queue not including the name of the machine or location depending on the address mode. - /// - public string Queue { get; private set; } - - /// - /// The (lowercase) name of the machine or the (normal) name of the location depending on the address mode. - /// - public string Machine { get; private set; } - - /// - /// Overloading for the == for the class Address - /// - /// Left hand side of == operator - /// Right hand side of == operator - /// true if the LHS is equal to RHS - public static bool operator ==(Address left, Address right) - { - return Equals(left, right); - } - - /// - /// Overloading for the != for the class Address - /// - /// Left hand side of != operator - /// Right hand side of != operator - /// true if the LHS is not equal to RHS - public static bool operator !=(Address left, Address right) - { - return !Equals(left, right); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified is equal to the current ; otherwise, false. - /// - /// The to compare with the current . 2 - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(Address)) return false; - return Equals((Address)obj); - } - - /// - /// Check this is equal to other Address - /// - /// reference addressed to be checked with this - /// true if this is equal to other - private bool Equals(Address other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - - if (!ignoreMachineName && !other.machineLowerCased.Equals(machineLowerCased)) - return false; - - return other.queueLowerCased.Equals(queueLowerCased); - } - - static string defaultMachine = RuntimeEnvironment.MachineName; - - readonly string queueLowerCased; - readonly string machineLowerCased; - static bool ignoreMachineName; - } -} diff --git a/src/NServiceBus.Core/AddressMode.cs b/src/NServiceBus.Core/AddressMode.cs index 8e2089e6b6c..c9a0fd0b65d 100644 --- a/src/NServiceBus.Core/AddressMode.cs +++ b/src/NServiceBus.Core/AddressMode.cs @@ -1,16 +1,17 @@ namespace NServiceBus { /// - /// Determines how the azure location behaves + /// Determines how the azure location behaves. /// public enum AddressMode { /// - /// Addressing behavior is confirm to local queuing policies, eg. MSMQ + /// Addressing behavior is confirm to local queuing policies, eg. MSMQ. /// Local, + /// - /// Addressing behavior is confirm to remote queuing policies, eg. Azure + /// Addressing behavior is confirm to remote queuing policies, eg. Azure. /// Remote } diff --git a/src/NServiceBus.Core/AllAssemblies.cs b/src/NServiceBus.Core/AllAssemblies.cs deleted file mode 100644 index 1a9011887f2..00000000000 --- a/src/NServiceBus.Core/AllAssemblies.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Reflection; - using System.Web; - using Hosting.Helpers; - - /// - /// Class for specifying which assemblies not to load. - /// - public class AllAssemblies : IExcludesBuilder, IIncludesBuilder - { - /// - /// Indicate that assemblies matching the given expression are not to be used. - /// Use the 'And' method to indicate other assemblies to be skipped. - /// - public static IExcludesBuilder Except(string assemblyExpression) - { - return new AllAssemblies - { - assembliesToExclude = - { - assemblyExpression - } - }; - } - - /// - /// Indicate that assemblies matching the given expression are to be used. - /// Use the 'And' method to indicate other assemblies to be included. - /// - public static IIncludesBuilder Matching(string assemblyExpression) - { - return new AllAssemblies - { - assembliesToInclude = - { - assemblyExpression - } - }; - } - - IExcludesBuilder IExcludesBuilder.And(string assemblyExpression) - { - if (!assembliesToExclude.Contains(assemblyExpression)) - { - assembliesToExclude.Add(assemblyExpression); - } - - return this; - } - - IExcludesBuilder IIncludesBuilder.Except(string assemblyExpression) - { - if (!assembliesToExclude.Contains(assemblyExpression)) - { - assembliesToExclude.Add(assemblyExpression); - } - - return this; - } - - IIncludesBuilder IIncludesBuilder.And(string assemblyExpression) - { - if (!assembliesToInclude.Contains(assemblyExpression)) - { - assembliesToInclude.Add(assemblyExpression); - } - - return this; - } - - /// - /// Returns an enumerator for looping over the assemblies to be loaded. - /// - public IEnumerator GetEnumerator() - { - var assemblyScanner = new AssemblyScanner(directory) - { - IncludeAppDomainAssemblies = true, - AssembliesToInclude = assembliesToInclude, - AssembliesToSkip = assembliesToExclude - }; - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); - - return assemblyScanner - .GetScannableAssemblies() - .Assemblies - .GetEnumerator(); - } - - /// - /// Return a non-generic enumerator. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - AllAssemblies() - { - var isWebApplication = HttpRuntime.AppDomainAppId != null; - - directory = isWebApplication - ? HttpRuntime.BinDirectory - : AppDomain.CurrentDomain.BaseDirectory; - } - - string directory; - List assembliesToExclude = new List(); - List assembliesToInclude = new List(); - } -} diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs index 7d33a5441a7..1835686e9c8 100644 --- a/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs @@ -41,7 +41,7 @@ public static void Verify(string licenseText) class SignedXmlVerifier { - readonly string publicKey; + string publicKey; public SignedXmlVerifier(string publicKey) { diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs index e29151cf0b1..f6a20f22f9b 100644 --- a/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs @@ -14,7 +14,7 @@ public static DateTime GetReleaseDate() if (attribute == null) { - throw new Exception("No ReleaseDateAttribute could be found in assembly, please make sure GitVersion is enabled"); + throw new Exception("No ReleaseDateAttribute could be found in assembly, ensure GitVersion is enabled"); } return UniversalDateParser.Parse((string)attribute.OriginalDate); diff --git a/src/NServiceBus.Core/Audit/Audit.cs b/src/NServiceBus.Core/Audit/Audit.cs index 0cee66f4f48..64f635fffa1 100644 --- a/src/NServiceBus.Core/Audit/Audit.cs +++ b/src/NServiceBus.Core/Audit/Audit.cs @@ -1,89 +1,30 @@ namespace NServiceBus.Features { - using System; - using System.Diagnostics; - using Config; - using Logging; - using Unicast.Queuing.Installers; - using Utils; + using Transport; /// /// Enabled message auditing for this endpoint. /// public class Audit : Feature - { + { internal Audit() { EnableByDefault(); - Prerequisite(config => GetConfiguredAuditQueue(config) != Address.Undefined,"No configured audit queue was found"); + Prerequisite(config => AuditConfigReader.GetConfiguredAuditQueue(config.Settings, out auditConfig), "No configured audit queue was found"); } + /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { - // If Audit feature is enabled and the value not specified via config and instead specified in the registry: - // Log a warning when running in the debugger to remind user to make sure the - // production machine will need to have the required registry setting. - if (Debugger.IsAttached && GetAuditQueueAddressFromAuditConfig(context) == Address.Undefined) - { - Logger.Warn("Endpoint auditing is configured using the registry on this machine, please ensure that you either run Set-NServiceBusLocalMachineSettings cmdlet on the target deployment machine or specify the QueueName attribute in the AuditConfig section in your app.config file. To quickly add the AuditConfig section to your app.config, in Package Manager Console type: add-NServiceBusAuditConfig."); - } - - context.Pipeline.Register(); - - var auditQueue = GetConfiguredAuditQueue(context); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.Enabled, true) - .ConfigureProperty(t => t.AuditQueue, auditQueue); - - var behaviorConfig = context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.AuditQueue, auditQueue); - - - - var messageAuditingConfig = context.Settings.GetConfigSection(); - if (messageAuditingConfig != null && messageAuditingConfig.OverrideTimeToBeReceived > TimeSpan.Zero) - { - behaviorConfig.ConfigureProperty(t => t.TimeToBeReceivedOnForwardedMessages, messageAuditingConfig.OverrideTimeToBeReceived); - } - } + context.Pipeline.Register(new AuditToDispatchConnector(auditConfig.TimeToBeReceived), "Dispatches the audit message to the transport"); + context.Pipeline.Register("AuditProcessedMessage", new InvokeAuditPipelineBehavior(auditConfig.Address), "Execute the audit pipeline"); - Address GetConfiguredAuditQueue(FeatureConfigurationContext context) - { - var auditAddress = GetAuditQueueAddressFromAuditConfig(context); - - if (auditAddress == Address.Undefined) - { - // Check to see if the audit queue has been specified either in the registry as a global setting - auditAddress = ReadAuditQueueNameFromRegistry(); - } - return auditAddress; - - } - - Address ReadAuditQueueNameFromRegistry() - { - var forwardQueue = RegistryReader.Read("AuditQueue"); - if (string.IsNullOrWhiteSpace(forwardQueue)) - { - return Address.Undefined; - } - return Address.Parse(forwardQueue); - } - - Address GetAuditQueueAddressFromAuditConfig(FeatureConfigurationContext context) - { - var messageAuditingConfig = context.Settings.GetConfigSection(); - if (messageAuditingConfig != null && !string.IsNullOrWhiteSpace(messageAuditingConfig.QueueName)) - { - return Address.Parse(messageAuditingConfig.QueueName); - } - return Address.Undefined; + context.Settings.Get().BindSending(auditConfig.Address); } - static ILog Logger = LogManager.GetLogger(); + AuditConfigReader.Result auditConfig; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/AuditBehavior.cs b/src/NServiceBus.Core/Audit/AuditBehavior.cs deleted file mode 100644 index 3d422a0eece..00000000000 --- a/src/NServiceBus.Core/Audit/AuditBehavior.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - using Transports; - using Unicast; - - - class AuditBehavior : IBehavior - { - public IAuditMessages MessageAuditer { get; set; } - - public Address AuditQueue { get; set; } - - public TimeSpan? TimeToBeReceivedOnForwardedMessages { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - next(); - - var sendOptions = new SendOptions(AuditQueue) - { - TimeToBeReceived = TimeToBeReceivedOnForwardedMessages - }; - - //set audit related headers - context.PhysicalMessage.Headers[Headers.ProcessingStarted] = DateTimeExtensions.ToWireFormattedString(context.Get("IncomingMessage.ProcessingStarted")); - context.PhysicalMessage.Headers[Headers.ProcessingEnded] = DateTimeExtensions.ToWireFormattedString(context.Get("IncomingMessage.ProcessingEnded")); - - MessageAuditer.Audit(sendOptions, context.PhysicalMessage); - } - - public class Registration:RegisterStep - { - public Registration() - : base(WellKnownStep.AuditProcessedMessage, typeof(AuditBehavior), "Send a copy of the successfully processed message to the configured audit queue") - { - InsertBefore(WellKnownStep.ProcessingStatistics); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/AuditConfig.cs b/src/NServiceBus.Core/Audit/AuditConfig.cs new file mode 100644 index 00000000000..8f5e2e2fb80 --- /dev/null +++ b/src/NServiceBus.Core/Audit/AuditConfig.cs @@ -0,0 +1,47 @@ +namespace NServiceBus.Config +{ + using System; + using System.Configuration; + + /// + /// Config section for the auditing feature. + /// + public class AuditConfig : ConfigurationSection + { + /// + /// Gets/sets the address to which messages received will be forwarded. + /// + [ConfigurationProperty("QueueName", IsRequired = false)] + public string QueueName + { + get + { + var result = this["QueueName"] as string; + if (string.IsNullOrWhiteSpace(result)) + { + return null; + } + + return result; + } + set { this["QueueName"] = value; } + } + + /// + /// Gets/sets the time to be received set on forwarded messages. + /// + [ConfigurationProperty("OverrideTimeToBeReceived", IsRequired = false)] + public TimeSpan OverrideTimeToBeReceived + { + get { return (TimeSpan) this["OverrideTimeToBeReceived"]; } + set + { + if (value < TimeSpan.Zero) + { + throw new ArgumentException("OverrideTimeToBeReceived must be Zero (to indicate empty) or greater.", nameof(value)); + } + this["OverrideTimeToBeReceived"] = value; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/AuditConfigReader.cs b/src/NServiceBus.Core/Audit/AuditConfigReader.cs new file mode 100644 index 00000000000..8641c776d4d --- /dev/null +++ b/src/NServiceBus.Core/Audit/AuditConfigReader.cs @@ -0,0 +1,107 @@ +namespace NServiceBus +{ + using System; + using System.Diagnostics; + using Config; + using Logging; + using Settings; + + /// + /// Utility class to find the configured audit queue for an endpoint. + /// + public static class AuditConfigReader + { + /// + /// Finds the configured audit queue for an endpoint. + /// The audit queue can be configured using 'EndpointConfiguration.AuditProcessedMessagesTo()', + /// via the 'QueueName' attribute of the 'Audit' config section + /// or by using the 'HKEY_LOCAL_MACHINE\SOFTWARE\ParticularSoftware\ServiceBus\AuditQueue' registry key. + /// + /// The configuration settings for the endpoint. + /// The configured audit queue address for the endpoint. + /// True if a configured audit address can be found, false otherwise. + public static bool TryGetAuditQueueAddress(this ReadOnlySettings settings, out string address) + { + Guard.AgainstNull(nameof(settings), settings); + + Result result; + if (!GetConfiguredAuditQueue(settings, out result)) + { + address = null; + return false; + } + + address = result.Address; + return true; + } + + internal static bool GetConfiguredAuditQueue(ReadOnlySettings settings, out Result result) + { + if (settings.TryGet(out result)) + { + return true; + } + + var auditConfig = settings.GetConfigSection(); + string address; + TimeSpan? timeToBeReceived = null; + if (auditConfig == null) + { + address = ReadAuditQueueNameFromRegistry(); + } + else + { + var ttbrOverride = auditConfig.OverrideTimeToBeReceived; + + if (ttbrOverride > TimeSpan.Zero) + { + timeToBeReceived = ttbrOverride; + } + if (string.IsNullOrWhiteSpace(auditConfig.QueueName)) + { + address = ReadAuditQueueNameFromRegistry(); + } + else + { + address = auditConfig.QueueName; + } + } + if (address == null) + { + result = null; + return false; + } + result = new Result + { + Address = address, + TimeToBeReceived = timeToBeReceived + }; + return true; + } + + static string ReadAuditQueueNameFromRegistry() + { + var queue = RegistryReader.Read("AuditQueue"); + if (string.IsNullOrWhiteSpace(queue)) + { + return null; + } + // If Audit feature is enabled and the value not specified via config and instead specified in the registry: + // Log a warning when running in the debugger to remind user to make sure the + // production machine will need to have the required registry setting. + if (Debugger.IsAttached) + { + Logger.Warn("Endpoint auditing is configured using the registry on this machine, see Particular Documentation for details on how to address this with your version of NServiceBus."); + } + return queue; + } + + static ILog Logger = LogManager.GetLogger(typeof(AuditConfigReader)); + + internal class Result + { + public string Address; + public TimeSpan? TimeToBeReceived; + } + } +} diff --git a/src/NServiceBus.Core/Audit/AuditContext.cs b/src/NServiceBus.Core/Audit/AuditContext.cs new file mode 100644 index 00000000000..910aa570e5c --- /dev/null +++ b/src/NServiceBus.Core/Audit/AuditContext.cs @@ -0,0 +1,41 @@ +namespace NServiceBus +{ + using Pipeline; + using Transport; + + class AuditContext : BehaviorContext, IAuditContext + { + public AuditContext(OutgoingMessage message, string auditAddress, IBehaviorContext parent) + : base(parent) + { + Guard.AgainstNull(nameof(message), message); + Guard.AgainstNullAndEmpty(nameof(auditAddress), auditAddress); + Message = message; + AuditAddress = auditAddress; + } + + public OutgoingMessage Message { get; } + + public string AuditAddress { get; } + + /// + /// Adds information about the current message that should be audited. + /// + /// The audit key. + /// The value. + public void AddAuditData(string key, string value) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + Guard.AgainstNullAndEmpty(nameof(value), value); + + AuditToDispatchConnector.State state; + + if (!Extensions.TryGet(out state)) + { + state = new AuditToDispatchConnector.State(); + Extensions.Set(state); + } + state.AuditValues[key] = value; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/AuditToDispatchConnector.cs b/src/NServiceBus.Core/Audit/AuditToDispatchConnector.cs new file mode 100644 index 00000000000..26c39eb94f2 --- /dev/null +++ b/src/NServiceBus.Core/Audit/AuditToDispatchConnector.cs @@ -0,0 +1,54 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using DeliveryConstraints; + using Performance.TimeToBeReceived; + using Pipeline; + using Routing; + + class AuditToDispatchConnector : StageConnector + { + public AuditToDispatchConnector(TimeSpan? timeToBeReceived) + { + this.timeToBeReceived = timeToBeReceived; + } + + public override Task Invoke(IAuditContext context, Func stage) + { + var message = context.Message; + + State state; + + if (context.Extensions.TryGet(out state)) + { + //transfer audit values to the headers of the messag to audit + foreach (var kvp in state.AuditValues) + { + message.Headers[kvp.Key] = kvp.Value; + } + } + + var deliveryConstraints = new List(); + + if (timeToBeReceived.HasValue) + { + deliveryConstraints.Add(new DiscardIfNotReceivedBefore(timeToBeReceived.Value)); + } + + var dispatchContext = this.CreateRoutingContext(context.Message, new UnicastRoutingStrategy(context.AuditAddress), context); + + dispatchContext.Extensions.Set(deliveryConstraints); + + return stage(dispatchContext); + } + + TimeSpan? timeToBeReceived; + + public class State + { + public Dictionary AuditValues = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/CausationMutator.cs b/src/NServiceBus.Core/Audit/CausationMutator.cs deleted file mode 100644 index 522673dcd65..00000000000 --- a/src/NServiceBus.Core/Audit/CausationMutator.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Unicast.Monitoring -{ - using MessageMutator; - using Messages; - - /// - /// Mutator to set the related to header - /// - class CausationMutator : IMutateOutgoingTransportMessages, INeedInitialization - { - /// - /// The bus is needed to get access to the current message id - /// - public IBus Bus { get; set; } - - /// - /// Keeps track of related messages to make auditing possible - /// - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - if (transportMessage.Headers.ContainsKey(Headers.ConversationId)) - return; - - var conversationId = CombGuid.Generate().ToString(); - - if (Bus.CurrentMessageContext != null) - { - transportMessage.Headers[Headers.RelatedTo] = Bus.CurrentMessageContext.Id; - - string conversationIdFromCurrentMessageContext; - if (Bus.CurrentMessageContext.Headers.TryGetValue(Headers.ConversationId, out conversationIdFromCurrentMessageContext)) - { - conversationId = conversationIdFromCurrentMessageContext; - } - } - - transportMessage.Headers[Headers.ConversationId] = conversationId; - } - - /// - /// Initializes - /// - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/ConfigureAudit.cs b/src/NServiceBus.Core/Audit/ConfigureAudit.cs new file mode 100644 index 00000000000..2a87c5efc07 --- /dev/null +++ b/src/NServiceBus.Core/Audit/ConfigureAudit.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + + /// + /// Contains extension methods to . + /// + public static class ConfigureAudit + { + /// + /// Configure Audit settings. + /// + /// The instance to apply the settings to. + /// The name of the audit queue to use. + /// The custom TTR to use for messages sent to the audit queue. + public static void AuditProcessedMessagesTo(this EndpointConfiguration config, string auditQueue, TimeSpan? timeToBeReceived = null) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(auditQueue), auditQueue); + if (timeToBeReceived != null) + { + Guard.AgainstNegative(nameof(timeToBeReceived), timeToBeReceived.Value); + } + config.Settings.Set(new AuditConfigReader.Result + { + Address = auditQueue, + TimeToBeReceived = timeToBeReceived + }); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/DefaultMessageAuditer.cs b/src/NServiceBus.Core/Audit/DefaultMessageAuditer.cs deleted file mode 100644 index 472747f26d2..00000000000 --- a/src/NServiceBus.Core/Audit/DefaultMessageAuditer.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace NServiceBus.Transports -{ - using System; - using NServiceBus.ObjectBuilder; - using Support; - using Unicast; - - class AuditerWrapper : IAuditMessages - { - readonly IBuilder builder; - - public Type AuditerImplType { get; set; } - - public AuditerWrapper(IBuilder builder) - { - this.builder = builder; - } - - public void Audit(SendOptions sendOptions, TransportMessage message) - { - ((dynamic)builder.Build(AuditerImplType)).Audit(sendOptions, message); - } - } - - class DefaultMessageAuditer - { - public ISendMessages MessageSender { get; set; } - - public Configure Configure { get; set; } - - public string EndpointName { get; set; } - - public void Audit(SendOptions sendOptions, TransportMessage transportMessage) - { - // Revert the original body if needed (if any mutators were applied, forward the original body as received) - transportMessage.RevertToOriginalBodyIfNeeded(); - - // Create a new transport message which will contain the appropriate headers - var messageToForward = new TransportMessage(transportMessage.Id, transportMessage.Headers) - { - Body = transportMessage.Body, - Recoverable = transportMessage.Recoverable, - TimeToBeReceived = sendOptions.TimeToBeReceived.HasValue ? sendOptions.TimeToBeReceived.Value : transportMessage.TimeToBeReceived - }; - - messageToForward.Headers[Headers.ProcessingMachine] = RuntimeEnvironment.MachineName; - messageToForward.Headers[Headers.ProcessingEndpoint] = EndpointName; - - if (transportMessage.ReplyToAddress != null) - { - messageToForward.Headers[Headers.OriginatingAddress] = transportMessage.ReplyToAddress.ToString(); - } - - // Send the newly created transport message to the queue - MessageSender.Send(messageToForward, new SendOptions(sendOptions.Destination) - { - ReplyToAddress = Configure.PublicReturnAddress - }); - } - - class Initialization : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.EndpointName, configuration.Settings.EndpointName())); - - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.AuditerImplType, typeof(DefaultMessageAuditer))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/IAuditContext.cs b/src/NServiceBus.Core/Audit/IAuditContext.cs new file mode 100644 index 00000000000..8b9c7e3f0f1 --- /dev/null +++ b/src/NServiceBus.Core/Audit/IAuditContext.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.Pipeline +{ + using Transport; + + /// + /// Provide context to behaviors on the audit pipeline. + /// + public interface IAuditContext : IBehaviorContext + { + /// + /// The message to be audited. + /// + OutgoingMessage Message { get; } + + /// + /// Address of the audit queue. + /// + string AuditAddress { get; } + + /// + /// Adds information about the current message that should be audited. + /// + /// The audit key. + /// The value. + void AddAuditData(string key, string value); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/IAuditMessages.cs b/src/NServiceBus.Core/Audit/IAuditMessages.cs deleted file mode 100644 index 9069f7aa361..00000000000 --- a/src/NServiceBus.Core/Audit/IAuditMessages.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus.Transports -{ - using Unicast; - - /// - /// Allows fine grained control on how messages are audited - /// - public interface IAuditMessages - { - /// - /// Called when a message should be sent to audit - /// - /// The send options of the message - /// The actual message - void Audit(SendOptions sendOptions,TransportMessage message); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs b/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs new file mode 100644 index 00000000000..6f47baa6adc --- /dev/null +++ b/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class InvokeAuditPipelineBehavior : IForkConnector + { + public InvokeAuditPipelineBehavior(string auditAddress) + { + this.auditAddress = auditAddress; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + await next(context).ConfigureAwait(false); + + context.Message.RevertToOriginalBodyIfNeeded(); + + var processedMessage = new OutgoingMessage(context.Message.MessageId, new Dictionary(context.Message.Headers), context.Message.Body); + + var auditContext = this.CreateAuditContext(processedMessage, auditAddress, context); + + await this.Fork(auditContext).ConfigureAwait(false); + } + + string auditAddress; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscribe.cs b/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscribe.cs deleted file mode 100644 index 744736a2a82..00000000000 --- a/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscribe.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace NServiceBus.Features -{ - using System.Linq; - using AutomaticSubscriptions; - using Logging; - using Transports; - - /// - /// Used to configure auto subscriptions. - /// - public class AutoSubscribe : Feature - { - internal AutoSubscribe() - { - EnableByDefault(); - - RegisterStartupTask(); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall); - - var transportDefinition = context.Settings.Get(); - - //if the transport has centralized pubsub we can auto-subscribe all events regardless if they have explicit routing or not - if (transportDefinition != null && transportDefinition.HasSupportForCentralizedPubSub) - { - context.Container.ConfigureProperty(s => s.DoNotRequireExplicitRouting, true); - } - - //apply any user specific settings - var targetType = typeof(AutoSubscriptionStrategy); - - foreach (var property in targetType.GetProperties()) - { - var settingsKey = targetType.FullName + "." + property.Name; - - if (context.Settings.HasSetting(settingsKey)) - { - context.Container.ConfigureProperty(property.Name, context.Settings.Get(settingsKey)); - } - } - } - - class ApplySubscriptions : FeatureStartupTask - { - public AutoSubscriptionStrategy AutoSubscriptionStrategy { get; set; } - - public IBus Bus { get; set; } - - public Conventions Conventions { get; set; } - - protected override void OnStart() - { - foreach (var eventType in AutoSubscriptionStrategy.GetEventsToSubscribe() - .Where(t => !Conventions.IsInSystemConventionList(t))) //never auto-subscribe system messages - { - Bus.Subscribe(eventType); - - Logger.DebugFormat("Auto subscribed to event {0}", eventType); - } - } - - static ILog Logger = LogManager.GetLogger(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscriptionStrategy.cs b/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscriptionStrategy.cs deleted file mode 100644 index f9ac7b8330f..00000000000 --- a/src/NServiceBus.Core/AutomaticSubscriptions/AutoSubscriptionStrategy.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace NServiceBus.AutomaticSubscriptions -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Unicast; - using Unicast.Routing; - - /// - /// The default strategy for auto subscriptions. - /// - class AutoSubscriptionStrategy - { - /// - /// The known handlers - /// - public IMessageHandlerRegistry HandlerRegistry { get; set; } - - /// - /// The message routing - /// - public StaticMessageRouter MessageRouter { get; set; } - - public Conventions Conventions { get; set; } - - /// - /// If set to true the endpoint will subscribe to it self even if no endpoint mappings exists - /// - public bool DoNotRequireExplicitRouting { get; set; } - - /// - /// if true messages that are handled by sagas wont be auto subscribed - /// - public bool DoNotAutoSubscribeSagas { get; set; } - - /// - /// If true all messages that are not commands will be auto subscribed - /// - public bool SubscribePlainMessages { get; set; } - - public IEnumerable GetEventsToSubscribe() - { - return HandlerRegistry.GetMessageTypes() - //get all potential messages - .Where(t => !Conventions.IsCommandType(t) && (SubscribePlainMessages || Conventions.IsEventType(t))) - //get messages that has routing if required - .Where(t => DoNotRequireExplicitRouting || MessageRouter.GetDestinationFor(t).Any()) - //get messages with other handlers than sagas if needed - .Where(t => !DoNotAutoSubscribeSagas || HandlerRegistry.GetHandlerTypes(t).Any(handler => !typeof(Saga.Saga).IsAssignableFrom(handler))) - .ToList(); - } - } - - - /// - /// Abstracts the strategy for selecting which events to auto-subscribe to during startup - /// - [ObsoleteEx( - Message = "Not an extension point any more. If you want full control over autosubscribe please turn the feature off and implement your own for-loop calling Bus.Subscribe() when starting your endpoint", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public interface IAutoSubscriptionStrategy - { - /// - /// Returns the list of events to auto-subscribe - /// - IEnumerable GetEventsToSubscribe(); - } - -} \ No newline at end of file diff --git a/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettings.cs b/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettings.cs deleted file mode 100644 index eac108c4530..00000000000 --- a/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettings.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus.AutomaticSubscriptions.Config -{ - /// - /// Provides fine grained control over auto subscribe - /// - public class AutoSubscribeSettings - { - BusConfiguration config; - - internal AutoSubscribeSettings(BusConfiguration config) - { - this.config = config; - } - - /// - /// Turns off auto subscriptions for sagas. Sagas where not auto subscribed by default before v4 - /// - public void DoNotAutoSubscribeSagas() - { - config.Settings.SetProperty(c => c.DoNotAutoSubscribeSagas, true); - } - - /// - /// Allows to endpoint to subscribe to messages owned by the local endpoint - /// - public void DoNotRequireExplicitRouting() - { - config.Settings.SetProperty(c => c.DoNotRequireExplicitRouting, true); - } - - /// - /// Turns on auto-subscriptions for messages not marked as commands. This was the default before v4 - /// - public void AutoSubscribePlainMessages() - { - config.Settings.SetProperty(c => c.SubscribePlainMessages, true); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettingsExtensions.cs b/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettingsExtensions.cs deleted file mode 100644 index 2778af33a30..00000000000 --- a/src/NServiceBus.Core/AutomaticSubscriptions/Config/AutoSubscribeSettingsExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus -{ - using AutomaticSubscriptions.Config; - - /// - /// Adds support for custom configuration of the auto subscribe feature - /// - public static class AutoSubscribeSettingsExtensions - { - /// - /// Use this method to change how auto subscribe works - /// - public static AutoSubscribeSettings AutoSubscribe(this BusConfiguration config) - { - return new AutoSubscribeSettings(config); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Bus.cs b/src/NServiceBus.Core/Bus.cs deleted file mode 100644 index 911ab77325d..00000000000 --- a/src/NServiceBus.Core/Bus.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NServiceBus -{ - using NServiceBus.Configuration.AdvanceExtensibility; - - /// - /// Factory for creating new bus instances - /// - public static class Bus - { - /// - /// Creates a bus instance with the given configuration - /// - /// The configuration to use - /// - public static IStartableBus Create(BusConfiguration configuration) - { - var config = configuration.BuildConfiguration(); - - config.Initialize(); - - return config.Builder.Build(); - } - - /// - /// Creates a bus instance to be used in send only mode - /// - /// The configuration to use - /// - public static ISendOnlyBus CreateSendOnly(BusConfiguration configuration) - { - configuration.GetSettings().Set("Endpoint.SendOnly", true); - - var config = configuration.BuildConfiguration(); - - config.Initialize(); - - return config.Builder.Build(); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/BusConfiguration.cs b/src/NServiceBus.Core/BusConfiguration.cs deleted file mode 100644 index 8ba51fcc252..00000000000 --- a/src/NServiceBus.Core/BusConfiguration.cs +++ /dev/null @@ -1,265 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Transactions; - using System.Web; - using NServiceBus.Config.ConfigurationSource; - using NServiceBus.Config.Conventions; - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Container; - using NServiceBus.Hosting.Helpers; - using NServiceBus.ObjectBuilder; - using NServiceBus.ObjectBuilder.Autofac; - using NServiceBus.ObjectBuilder.Common; - using NServiceBus.Pipeline; - using NServiceBus.Settings; - using NServiceBus.Utils; - using NServiceBus.Utils.Reflection; - - /// - /// Configuration used to create a bus instance - /// - public class BusConfiguration : ExposeSettings - { - /// - /// Initializes a fresh instance of the builder - /// - public BusConfiguration() - : base(new SettingsHolder()) - { - configurationSourceToUse = new DefaultConfigurationSource(); - Settings.Set(new PipelineModifications()); - Pipeline = new PipelineSettings(this); - - Settings.SetDefault("Endpoint.SendOnly", false); - Settings.SetDefault("Transactions.Enabled", true); - Settings.SetDefault("Transactions.IsolationLevel", IsolationLevel.ReadCommitted); - Settings.SetDefault("Transactions.DefaultTimeout", TransactionManager.DefaultTimeout); - Settings.SetDefault("Transactions.SuppressDistributedTransactions", false); - Settings.SetDefault("Transactions.DoNotWrapHandlersExecutionInATransactionScope", false); - } - - /// - /// Access to the pipeline configuration - /// - public PipelineSettings Pipeline { get; private set; } - - /// - /// Used to configure components in the container. - /// - public void RegisterComponents(Action registration) - { - registrations.Add(registration); - } - - /// - /// Specifies the range of types that NServiceBus scans for handlers etc. - /// - public void TypesToScan(IEnumerable typesToScan) - { - scannedTypes = typesToScan.ToList(); - } - - /// - /// The assemblies to include when scanning for types. - /// - public void AssembliesToScan(IEnumerable assemblies) - { - AssembliesToScan(assemblies.ToArray()); - } - - /// - /// The assemblies to include when scanning for types. - /// - public void AssembliesToScan(params Assembly[] assemblies) - { - scannedTypes = Configure.GetAllowedTypes(assemblies); - } - - /// - /// Specifies the directory where NServiceBus scans for types. - /// - public void ScanAssembliesInDirectory(string probeDirectory) - { - directory = probeDirectory; - AssembliesToScan(GetAssembliesInDirectory(probeDirectory)); - } - - /// - /// Overrides the default configuration source. - /// - public void CustomConfigurationSource(IConfigurationSource configurationSource) - { - configurationSourceToUse = configurationSource; - } - - /// - /// Defines the name to use for this endpoint. - /// - public void EndpointName(string name) - { - endpointName = name; - } - - /// - /// Defines the version of this endpoint. - /// - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5.2", Message = "This api does not do anything.")] - public void EndpointVersion(string version) - { - endpointVersion = version; - } - - /// - /// Defines the conventions to use for this endpoint. - /// - public ConventionsBuilder Conventions() - { - return conventionsBuilder; - } - - /// - /// Defines a custom builder to use - /// - /// The builder type - public void UseContainer(Action customizations = null) where T : ContainerDefinition, new() - { - if (customizations != null) - { - customizations(new ContainerCustomizations(Settings)); - } - - UseContainer(typeof(T)); - } - - /// - /// Defines a custom builder to use - /// - /// The type of the builder - public void UseContainer(Type definitionType) - { - Guard.TypeHasDefaultConstructor(definitionType, "definitionType"); - - UseContainer(definitionType.Construct().CreateContainer(Settings)); - } - - /// - /// Uses an already active instance of a builder - /// - /// The instance to use - public void UseContainer(IContainer builder) - { - customBuilder = builder; - } - - /// - /// Sets the public return address of this endpoint. - /// - /// The public address. - public void OverridePublicReturnAddress(Address address) - { - publicReturnAddress = address; - } - - /// - /// Sets the address of this endpoint. - /// - /// The queue name. - public void OverrideLocalAddress(string queue) - { - Settings.Set("NServiceBus.LocalAddress", queue); - } - - /// - /// Creates the configuration object - /// - internal Configure BuildConfiguration() - { - if (scannedTypes == null) - { - var directoryToScan = AppDomain.CurrentDomain.BaseDirectory; - if (HttpRuntime.AppDomainAppId != null) - { - directoryToScan = HttpRuntime.BinDirectory; - } - - ScanAssembliesInDirectory(directoryToScan); - } - - scannedTypes = scannedTypes.Union(Configure.GetAllowedTypes(Assembly.GetExecutingAssembly())).ToList(); - - if (HttpRuntime.AppDomainAppId == null) - { - var baseDirectory = directory ?? AppDomain.CurrentDomain.BaseDirectory; - var hostPath = Path.Combine(baseDirectory, "NServiceBus.Host.exe"); - if (File.Exists(hostPath)) - { - scannedTypes = scannedTypes.Union(Configure.GetAllowedTypes(Assembly.LoadFrom(hostPath))).ToList(); - } - } - - Settings.SetDefault("TypesToScan", scannedTypes); - - Configure.ActivateAndInvoke(scannedTypes, t => t.Customize(this)); - - UseTransportExtensions.SetupTransport(this); - var container = customBuilder ?? new AutofacObjectBuilder(); - - Settings.SetDefault(configurationSourceToUse); - - var endpointHelper = new EndpointHelper(new StackTrace()); - - if (endpointVersion == null) - { - endpointVersion = endpointHelper.GetEndpointVersion(); - } - - if (endpointName == null) - { - endpointName = endpointHelper.GetDefaultEndpointName(); - } - - Settings.SetDefault("EndpointName", endpointName); - Settings.SetDefault("EndpointVersion", endpointVersion); - - if (publicReturnAddress != null) - { - Settings.SetDefault("PublicReturnAddress", publicReturnAddress); - } - - container.RegisterSingleton(typeof(Conventions), conventionsBuilder.Conventions); - - Settings.SetDefault(conventionsBuilder.Conventions); - - return new Configure(Settings, container, registrations, Pipeline); - } - - IEnumerable GetAssembliesInDirectory(string path, params string[] assembliesToSkip) - { - var assemblyScanner = new AssemblyScanner(path); - assemblyScanner.MustReferenceAtLeastOneAssembly.Add(typeof(IHandleMessages<>).Assembly); - if (assembliesToSkip != null) - { - assemblyScanner.AssembliesToSkip = assembliesToSkip.ToList(); - } - return assemblyScanner - .GetScannableAssemblies() - .Assemblies; - } - - IConfigurationSource configurationSourceToUse; - ConventionsBuilder conventionsBuilder = new ConventionsBuilder(); - List> registrations = new List>(); - IContainer customBuilder; - string directory; - string endpointName; - string endpointVersion; - IList scannedTypes; - Address publicReturnAddress; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/BusNotifications.cs b/src/NServiceBus.Core/BusNotifications.cs deleted file mode 100644 index 719e17760ef..00000000000 --- a/src/NServiceBus.Core/BusNotifications.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Faults; - using NServiceBus.Pipeline; - - /// - /// Bus notifications. - /// - public class BusNotifications: IDisposable - { - /// - /// Errors push-based notifications - /// - public ErrorsNotifications Errors - { - get { return errorNotifications; } - } - - ErrorsNotifications errorNotifications = new ErrorsNotifications(); - - /// - /// Pipeline push-based notifications - /// - public PipelineNotifications Pipeline - { - get { return pipeNotifications; } - } - - PipelineNotifications pipeNotifications = new PipelineNotifications(); - - void IDisposable.Dispose() - { - // Injected - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Causation/AttachCausationHeadersBehavior.cs b/src/NServiceBus.Core/Causation/AttachCausationHeadersBehavior.cs new file mode 100644 index 00000000000..3fb65c37256 --- /dev/null +++ b/src/NServiceBus.Core/Causation/AttachCausationHeadersBehavior.cs @@ -0,0 +1,37 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class AttachCausationHeadersBehavior : IBehavior + { + public Task Invoke(IOutgoingPhysicalMessageContext context, Func next) + { + ApplyHeaders(context); + + return next(context); + } + + static void ApplyHeaders(IOutgoingPhysicalMessageContext context) + { + var conversationId = CombGuid.Generate().ToString(); + + IncomingMessage incomingMessage; + + if (context.TryGetIncomingPhysicalMessage(out incomingMessage)) + { + context.Headers[Headers.RelatedTo] = incomingMessage.MessageId; + + string conversationIdFromCurrentMessageContext; + if (incomingMessage.Headers.TryGetValue(Headers.ConversationId, out conversationIdFromCurrentMessageContext)) + { + conversationId = conversationIdFromCurrentMessageContext; + } + } + + context.Headers[Headers.ConversationId] = conversationId; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Causation/MessageCausation.cs b/src/NServiceBus.Core/Causation/MessageCausation.cs new file mode 100644 index 00000000000..bfca5f2dae4 --- /dev/null +++ b/src/NServiceBus.Core/Causation/MessageCausation.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Features +{ + class MessageCausation : Feature + { + public MessageCausation() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Pipeline.Register("AttachCausationHeaders", new AttachCausationHeadersBehavior(), "Adds related to and conversation id headers to outgoing messages"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/CircuitBreakers/CircuitBreaker.cs b/src/NServiceBus.Core/CircuitBreakers/CircuitBreaker.cs index 585d7d9a175..382d61f3bfd 100644 --- a/src/NServiceBus.Core/CircuitBreakers/CircuitBreaker.cs +++ b/src/NServiceBus.Core/CircuitBreakers/CircuitBreaker.cs @@ -1,34 +1,21 @@ -namespace NServiceBus.CircuitBreakers +namespace NServiceBus { using System; using System.Threading; - /// - /// A circuit breaker implementation. - /// class CircuitBreaker : IDisposable { - readonly int threshold; - int firedTimes; - // ReSharper disable once NotAccessedField.Local - Timer timer; - int failureCount; - - /// - /// Create a . - /// - /// Number of triggers before it fires. - /// The to wait before resetting the . public CircuitBreaker(int threshold, TimeSpan resetEvery) { this.threshold = threshold; timer = new Timer(state => failureCount = 0, null, resetEvery, resetEvery); } - /// - /// Method to execute. - /// - /// The callback to execute. + public void Dispose() + { + //Injected + } + public void Execute(Action trigger) { if (Interlocked.Increment(ref failureCount) > threshold) @@ -40,9 +27,10 @@ public void Execute(Action trigger) } } - public void Dispose() - { - //Injected - } + int failureCount; + int firedTimes; + int threshold; + // ReSharper disable once NotAccessedField.Local + Timer timer; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/CircuitBreakers/ICircuitBreaker.cs b/src/NServiceBus.Core/CircuitBreakers/ICircuitBreaker.cs new file mode 100644 index 00000000000..d64aeb70c9c --- /dev/null +++ b/src/NServiceBus.Core/CircuitBreakers/ICircuitBreaker.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + interface ICircuitBreaker + { + void Success(); + Task Failure(Exception exception); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/CircuitBreakers/RepeatedFailuresOverTimeCircuitBreaker.cs b/src/NServiceBus.Core/CircuitBreakers/RepeatedFailuresOverTimeCircuitBreaker.cs index 47d91ffb8c6..998becf87b4 100644 --- a/src/NServiceBus.Core/CircuitBreakers/RepeatedFailuresOverTimeCircuitBreaker.cs +++ b/src/NServiceBus.Core/CircuitBreakers/RepeatedFailuresOverTimeCircuitBreaker.cs @@ -1,67 +1,35 @@ -namespace NServiceBus.CircuitBreakers +namespace NServiceBus { using System; using System.Threading; + using System.Threading.Tasks; using Logging; - /// - /// A circuit breaker that triggers after a given time - /// - public class RepeatedFailuresOverTimeCircuitBreaker : IDisposable + class RepeatedFailuresOverTimeCircuitBreaker : IDisposable, ICircuitBreaker { - /// - /// Ctor - /// - /// - /// - /// - public RepeatedFailuresOverTimeCircuitBreaker(string name, TimeSpan timeToWaitBeforeTriggering, - Action triggerAction) - : this(name, timeToWaitBeforeTriggering, triggerAction, TimeSpan.FromSeconds(1)) - { - } - - /// - /// Ctor - /// - /// - /// - /// - /// - public RepeatedFailuresOverTimeCircuitBreaker(string name, TimeSpan timeToWaitBeforeTriggering, - Action triggerAction, TimeSpan delayAfterFailure) + public RepeatedFailuresOverTimeCircuitBreaker(string name, TimeSpan timeToWaitBeforeTriggering, Action triggerAction) { this.name = name; - this.delayAfterFailure = delayAfterFailure; this.triggerAction = triggerAction; this.timeToWaitBeforeTriggering = timeToWaitBeforeTriggering; timer = new Timer(CircuitBreakerTriggered); } - /// - /// Tell the CB that it should disarm - /// - public bool Success() + public void Success() { var oldValue = Interlocked.Exchange(ref failureCount, 0); if (oldValue == 0) { - return false; + return; } - timer.Change(Timeout.Infinite, Timeout.Infinite); + timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); Logger.InfoFormat("The circuit breaker for {0} is now disarmed", name); - - return true; } - /// - /// Tells the CB to arm - /// - /// - public void Failure(Exception exception) + public Task Failure(Exception exception) { lastException = exception; var newValue = Interlocked.Increment(ref failureCount); @@ -69,16 +37,12 @@ public void Failure(Exception exception) if (newValue == 1) { timer.Change(timeToWaitBeforeTriggering, NoPeriodicTriggering); - Logger.InfoFormat("The circuit breaker for {0} is now in the armed state", name); + Logger.WarnFormat("The circuit breaker for {0} is now in the armed state", name); } - - Thread.Sleep(delayAfterFailure); + return Task.Delay(TimeSpan.FromSeconds(1)); } - /// - /// Disposes the CB - /// public void Dispose() { //Injected @@ -93,15 +57,15 @@ void CircuitBreakerTriggered(object state) } } - static readonly TimeSpan NoPeriodicTriggering = TimeSpan.FromMilliseconds(-1); - static ILog Logger = LogManager.GetLogger(); - - readonly TimeSpan delayAfterFailure; - readonly string name; - TimeSpan timeToWaitBeforeTriggering; - Timer timer; - readonly Action triggerAction; long failureCount; Exception lastException; + + string name; + Timer timer; + TimeSpan timeToWaitBeforeTriggering; + Action triggerAction; + + static TimeSpan NoPeriodicTriggering = TimeSpan.FromMilliseconds(-1); + static ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/CompletionResult.cs b/src/NServiceBus.Core/CompletionResult.cs deleted file mode 100644 index b3a1b7495ae..00000000000 --- a/src/NServiceBus.Core/CompletionResult.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// The object found in .AsyncState returned by the - /// passed to the Send method of the bus. - /// - public class CompletionResult - { - /// - /// If was called, this contains the value passed to it. - /// - public int ErrorCode { get; set; } - - /// - /// If was called, this contains the messages passed to it. - /// - public object[] Messages { get; set; } - - /// - /// An object that can contain state information for the method. - /// - public object State { get; set; } - } -} diff --git a/src/NServiceBus.Core/Config/Advanced/ConfigureSettingLocalAddressNameAction_Obsolete.cs b/src/NServiceBus.Core/Config/Advanced/ConfigureSettingLocalAddressNameAction_Obsolete.cs deleted file mode 100644 index 6fc42cc5ef4..00000000000 --- a/src/NServiceBus.Core/Config/Advanced/ConfigureSettingLocalAddressNameAction_Obsolete.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureSettingLocalAddressNameAction - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Queue name is controlled by the endpoint name. The endpoint name can be configured using a `EndpointNameAttribute`, by passing a serviceName parameter to the host or calling `BusConfiguration.EndpointName` in the fluent API.")] - public static Configure DefineLocalAddressNameFunc(this Configure config, Func setLocalAddressNameFunc) - { - throw new NotImplementedException(); - } - } -} - diff --git a/src/NServiceBus.Core/Config/AuditConfig.cs b/src/NServiceBus.Core/Config/AuditConfig.cs deleted file mode 100644 index 4144c5e2d33..00000000000 --- a/src/NServiceBus.Core/Config/AuditConfig.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus.Config -{ - using System; - using System.Configuration; - - /// - /// Config section for the auditing feature - /// - public class AuditConfig : ConfigurationSection - { - /// - /// Gets/sets the address to which messages received will be forwarded. - /// - [ConfigurationProperty("QueueName", IsRequired = false)] - public string QueueName - { - get - { - var result = this["QueueName"] as string; - if (string.IsNullOrWhiteSpace(result)) - result = null; - - return result; - } - set - { - this["QueueName"] = value; - } - } - - /// - /// Gets/sets the time to be received set on forwarded messages - /// - [ConfigurationProperty("OverrideTimeToBeReceived", IsRequired = false)] - public TimeSpan OverrideTimeToBeReceived - { - get - { - return (TimeSpan)this["OverrideTimeToBeReceived"]; - } - set - { - this["OverrideTimeToBeReceived"] = value; - } - } - } -} diff --git a/src/NServiceBus.Core/Config/ConfigureExtensions.cs b/src/NServiceBus.Core/Config/ConfigureExtensions.cs deleted file mode 100644 index af9dab82863..00000000000 --- a/src/NServiceBus.Core/Config/ConfigureExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class ConfigureExtensions - { - [ObsoleteEx( - Replacement = "Bus.CreateSendOnly(new BusConfiguration())", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static IBus SendOnly(this Configure config) - { - throw new InvalidOperationException(); - } - } -} diff --git a/src/NServiceBus.Core/Config/Conventions/EndpointHelper.cs b/src/NServiceBus.Core/Config/Conventions/EndpointHelper.cs deleted file mode 100644 index 7399eb10f4c..00000000000 --- a/src/NServiceBus.Core/Config/Conventions/EndpointHelper.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace NServiceBus.Config.Conventions -{ - using System; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Web; - - class EndpointHelper - { - StackTrace stackTraceToExamine; - Type entryType; - bool initialized; - - public EndpointHelper(StackTrace stackTraceToExamine) - { - this.stackTraceToExamine = stackTraceToExamine; - } - - /// - /// Gets the name of this endpoint - /// - /// The name of the endpoint. - public string GetDefaultEndpointName() - { - Initialize(); - - string endpointName = null; - - if (entryType != null) - { - endpointName = entryType.Namespace ?? entryType.Assembly.GetName().Name; - } - - if (endpointName == null) - { - throw new InvalidOperationException( - "No endpoint name could be generated, please specify your own convention using Configure.DefineEndpointName()"); - } - - return endpointName; - } - - /// - /// Gets the version of the endpoint. - /// - /// The the endpoint. - public string GetEndpointVersion() - { - Initialize(); - - if (entryType != null) - { - return FileVersionRetriever.GetFileVersion(entryType); - } - - throw new InvalidOperationException( - "No version of the endpoint could not be retrieved using the default convention, please specify your own version using config.EndpointVersion(version)."); - } - - void Initialize() - { - if (initialized) - return; - try - { - var entryAssembly = Assembly.GetEntryAssembly(); - if (entryAssembly != null && entryAssembly.EntryPoint != null) - { - entryType = entryAssembly.EntryPoint.ReflectedType; - return; - } - - StackFrame targetFrame = null; - - var stackFrames = new StackTrace().GetFrames(); - if (stackFrames != null) - { - targetFrame = - stackFrames.FirstOrDefault( - f => typeof(HttpApplication).IsAssignableFrom(f.GetMethod().DeclaringType)); - } - - if (targetFrame != null) - { - entryType = targetFrame.GetMethod().ReflectedType; - return; - } - - if (stackTraceToExamine != null) - { - stackFrames = stackTraceToExamine.GetFrames(); - if (stackFrames != null) - { - targetFrame = - stackFrames.FirstOrDefault( - f => - { - var declaringType = f.GetMethod().DeclaringType; - return declaringType != typeof(Configure) && declaringType != typeof(BusConfiguration); - }); - } - } - - if (targetFrame == null) - targetFrame = stackFrames.FirstOrDefault( - f => f.GetMethod().DeclaringType.Assembly != typeof(Configure).Assembly); - - if (targetFrame != null) - { - entryType = targetFrame.GetMethod().ReflectedType; - } - } - finally - { - initialized = true; - } - } - } -} diff --git a/src/NServiceBus.Core/Config/IFinalizeConfiguration.cs b/src/NServiceBus.Core/Config/IFinalizeConfiguration.cs deleted file mode 100644 index 2db1f26159e..00000000000 --- a/src/NServiceBus.Core/Config/IFinalizeConfiguration.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus.Config -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "`IFinalizeConfiguration` is no longer in use. Please use the Feature concept instead")] - public interface IFinalizeConfiguration - { - void FinalizeConfiguration(Configure config); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Config/IWantToRunWhenConfigurationIsComplete.cs b/src/NServiceBus.Core/Config/IWantToRunWhenConfigurationIsComplete.cs deleted file mode 100644 index d43b64353b4..00000000000 --- a/src/NServiceBus.Core/Config/IWantToRunWhenConfigurationIsComplete.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus.Config -{ - /// - /// Implementors are invoked when configuration is complete. - /// Implementors are resolved from the container so have access to full DI. - /// - public interface IWantToRunWhenConfigurationIsComplete - { - /// - /// Method invoked to run custom code. - /// - void Run(Configure config); - } -} diff --git a/src/NServiceBus.Core/Config/MessageEndpointMapping.cs b/src/NServiceBus.Core/Config/MessageEndpointMapping.cs deleted file mode 100644 index 351b31dfe02..00000000000 --- a/src/NServiceBus.Core/Config/MessageEndpointMapping.cs +++ /dev/null @@ -1,235 +0,0 @@ -namespace NServiceBus.Config -{ - using System; - using System.Configuration; - using System.IO; - using System.Linq; - using System.Reflection; - - /// - /// A configuration element representing which message types map to which endpoint. - /// - public class MessageEndpointMapping : ConfigurationElement, IComparable - { - /// - /// A string defining the message assembly, or single message type. - /// - [ConfigurationProperty("Messages", IsRequired = false)] - public string Messages - { - get - { - return (string)this["Messages"]; - } - set - { - this["Messages"] = value; - } - } - - /// - /// The endpoint named according to "queue@machine". - /// - [ConfigurationProperty("Endpoint", IsRequired = true)] - public string Endpoint - { - get - { - return (string)this["Endpoint"]; - } - set - { - this["Endpoint"] = value; - } - } - - /// - /// The message assembly for the endpoint mapping. - /// - [ConfigurationProperty("Assembly", IsRequired = false)] - public string AssemblyName - { - get - { - return (string)this["Assembly"]; - } - set - { - this["Assembly"] = value; - } - } - - /// - /// The fully qualified name of the message type. Define this if you want to map a single message type to the endpoint. - /// - /// Type will take preference above namespace - [ConfigurationProperty("Type", IsRequired = false)] - public string TypeFullName - { - get - { - return (string)this["Type"]; - } - set - { - this["Type"] = value; - } - } - - /// - /// The message type. Define this if you want to map all the types in the namespace to the endpoint. - /// - /// Sub-namespaces will not be mapped. - [ConfigurationProperty("Namespace", IsRequired = false)] - public string Namespace - { - get - { - return (string)this["Namespace"]; - } - set - { - this["Namespace"] = value; - } - } - - /// - /// Uses the configuration properties to configure the endpoint mapping - /// - public void Configure(Action mapTypeToEndpoint) - { - if (!string.IsNullOrWhiteSpace(Messages)) - { - ConfigureEndpointMappingUsingMessagesProperty(mapTypeToEndpoint); - return; - } - - var address = Address.Parse(Endpoint); - var assemblyName = AssemblyName; - var ns = Namespace; - var typeFullName = TypeFullName; - - if (string.IsNullOrWhiteSpace(assemblyName)) - throw new ArgumentException("Could not process message endpoint mapping. The Assembly property is not defined. Either the Assembly or Messages property is required."); - - var a = GetMessageAssembly(assemblyName); - - if (!string.IsNullOrWhiteSpace(typeFullName)) - { - try - { - var t = a.GetType(typeFullName, false); - - if (t == null) - throw new ArgumentException(string.Format("Could not process message endpoint mapping. Cannot find the type '{0}' in the assembly '{1}'. Ensure that you are using the full name for the type.", typeFullName, assemblyName)); - - mapTypeToEndpoint(t, address); - - return; - } - catch (BadImageFormatException ex) - { - throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type '{0}' in the assembly '{1}'", typeFullName, assemblyName), ex); - } - catch (FileLoadException ex) - { - throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type '{0}' in the assembly '{1}'", typeFullName, assemblyName), ex); - } - } - - var messageTypes = a.GetTypes().AsQueryable(); - - if (!string.IsNullOrEmpty(ns)) - messageTypes = messageTypes.Where(t => !string.IsNullOrWhiteSpace(t.Namespace) && t.Namespace.Equals(ns, StringComparison.InvariantCultureIgnoreCase)); - - foreach (var t in messageTypes) - mapTypeToEndpoint(t, address); - } - - void ConfigureEndpointMappingUsingMessagesProperty(Action mapTypeToEndpoint) - { - var address = Address.Parse(Endpoint); - var messages = Messages; - - try - { - var messageType = Type.GetType(messages, false); - if (messageType != null) - { - mapTypeToEndpoint(messageType, address); - return; - } - } - catch (BadImageFormatException ex) - { - throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type: " + messages), ex); - } - catch (FileLoadException ex) - { - throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type: " + messages), ex); - } - - var messagesAssembly = GetMessageAssembly(messages); - - foreach (var t in messagesAssembly.GetTypes()) - mapTypeToEndpoint(t, address); - } - - private static Assembly GetMessageAssembly(string assemblyName) - { - try - { - return Assembly.Load(assemblyName); - } - catch (Exception ex) - { - throw new ArgumentException("Could not process message endpoint mapping. Problem loading message assembly: " + assemblyName, ex); - } - } - - /// - /// Comparison support - /// - /// - public int CompareTo(MessageEndpointMapping other) - { - if (!String.IsNullOrWhiteSpace(TypeFullName) || HaveMessagesMappingWithType(this)) - { - if (!String.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) - return 0; - - return -1; - } - - if (!String.IsNullOrWhiteSpace(Namespace)) - { - if (!String.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) - return 1; - - if (!String.IsNullOrWhiteSpace(other.Namespace)) - return 0; - - return -1; - } - - if (!String.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) - return 1; - - if (!String.IsNullOrWhiteSpace(other.Namespace)) - return 1; - - if (!String.IsNullOrWhiteSpace(other.AssemblyName) || !String.IsNullOrWhiteSpace(other.Messages)) - return 0; - - return -1; - } - - private static bool HaveMessagesMappingWithType(MessageEndpointMapping mapping) - { - if (String.IsNullOrWhiteSpace(mapping.Messages)) - return false; - - return Type.GetType(mapping.Messages, false) != null; - } - } -} diff --git a/src/NServiceBus.Core/Config/MessageEndpointMappingCollection.cs b/src/NServiceBus.Core/Config/MessageEndpointMappingCollection.cs deleted file mode 100644 index 1513388a2fe..00000000000 --- a/src/NServiceBus.Core/Config/MessageEndpointMappingCollection.cs +++ /dev/null @@ -1,189 +0,0 @@ -namespace NServiceBus.Config -{ - using System; - using System.Configuration; - - /// - /// A configuration element collection of MessageEndpointMappings. - /// - public class MessageEndpointMappingCollection : ConfigurationElementCollection - { - /// - /// Returns AddRemoveClearMap. - /// - public override ConfigurationElementCollectionType CollectionType - { - get - { - return - ConfigurationElementCollectionType.AddRemoveClearMap; - } - } - - /// - /// Creates a new MessageEndpointMapping. - /// - protected override ConfigurationElement CreateNewElement() - { - return new MessageEndpointMapping(); - } - - /// - /// Creates a new MessageEndpointMapping, setting its Message property to the given name. - /// - protected override ConfigurationElement CreateNewElement(string elementName) - { - var result = new MessageEndpointMapping {Messages = elementName}; - - return result; - } - - /// - /// Returns the Messages property of the given MessageEndpointMapping element. - /// - protected override Object GetElementKey(ConfigurationElement element) - { - var messageEndpointMapping = (MessageEndpointMapping) element; - - return String.Format("{0}{1}{2}{3}", messageEndpointMapping.Messages, messageEndpointMapping.AssemblyName, messageEndpointMapping.TypeFullName, messageEndpointMapping.Namespace); - } - - /// - /// Calls the base AddElementName. - /// - public new string AddElementName - { - get - { return base.AddElementName; } - - set - { base.AddElementName = value; } - - } - - /// - /// Calls the base ClearElementName. - /// - public new string ClearElementName - { - get - { return base.ClearElementName; } - - set - { base.AddElementName = value; } - - } - - /// - /// Returns the base RemoveElementName. - /// - public new string RemoveElementName - { - get - { return base.RemoveElementName; } - } - - /// - /// Returns the base Count. - /// - public new int Count - { - get { return base.Count; } - } - - /// - /// Gets/sets the MessageEndpointMapping at the given index. - /// - public MessageEndpointMapping this[int index] - { - get - { - return (MessageEndpointMapping)BaseGet(index); - } - set - { - if (BaseGet(index) != null) - { - BaseRemoveAt(index); - } - BaseAdd(index, value); - } - } - - /// - /// Gets the MessageEndpointMapping for the given name. - /// - new public MessageEndpointMapping this[string Name] - { - get - { - return (MessageEndpointMapping)BaseGet(Name); - } - } - - /// - /// Calls BaseIndexOf on the given mapping. - /// - public int IndexOf(MessageEndpointMapping mapping) - { - return BaseIndexOf(mapping); - } - - /// - /// Calls BaseAdd. - /// - public void Add(MessageEndpointMapping mapping) - { - BaseAdd(mapping); - } - - /// - /// Calls BaseAdd with true as the additional parameter. - /// - protected override void BaseAdd(ConfigurationElement element) - { - BaseAdd(element, true); - } - - /// - /// If the mapping exists, calls BaseRemove on it. - /// - public void Remove(MessageEndpointMapping mapping) - { - if (BaseIndexOf(mapping) >= 0) - BaseRemove(mapping.Messages); - } - - /// - /// Calls BaseRemoveAt. - /// - public void RemoveAt(int index) - { - BaseRemoveAt(index); - } - - /// - /// Calls BaseRemove. - /// - public void Remove(string name) - { - BaseRemove(name); - } - - /// - /// Calls BaseClear. - /// - public void Clear() - { - BaseClear(); - } - - /// - /// True if the collection is readonly - /// - public override bool IsReadOnly() - { - return false; - } - } -} diff --git a/src/NServiceBus.Core/Config/MessageForwardingInCaseOfFaultConfig.cs b/src/NServiceBus.Core/Config/MessageForwardingInCaseOfFaultConfig.cs deleted file mode 100644 index 12eaad371bb..00000000000 --- a/src/NServiceBus.Core/Config/MessageForwardingInCaseOfFaultConfig.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Config -{ - using System.Configuration; - - /// - /// Message Forwarding In Case Of Fault Config - /// - public class MessageForwardingInCaseOfFaultConfig : ConfigurationSection - { - /// - /// The queue to which errors will be forwarded. - /// - [ConfigurationProperty("ErrorQueue", IsRequired = true)] - public string ErrorQueue - { - get - { - return this["ErrorQueue"] as string; - } - set - { - this["ErrorQueue"] = value; - } - } - } -} diff --git a/src/NServiceBus.Core/Config/MsmqMessageQueueConfig.cs b/src/NServiceBus.Core/Config/MsmqMessageQueueConfig.cs deleted file mode 100644 index 84e620fd23f..00000000000 --- a/src/NServiceBus.Core/Config/MsmqMessageQueueConfig.cs +++ /dev/null @@ -1,36 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus.Config -{ - using System.Configuration; - - [ObsoleteEx(Message = "Use NServiceBus/Transport connectionString instead.", TreatAsErrorFromVersion = "5.0", RemoveInVersion = "6.0")] - public class MsmqMessageQueueConfig : ConfigurationSection - { - - [ConfigurationProperty("UseDeadLetterQueue", IsRequired = false, DefaultValue = true)] - public bool UseDeadLetterQueue - { - get - { - return (bool)this["UseDeadLetterQueue"]; - } - set - { - this["UseDeadLetterQueue"] = value; - } - } - - [ConfigurationProperty("UseJournalQueue", IsRequired = false)] - public bool UseJournalQueue - { - get - { - return (bool)this["UseJournalQueue"]; - } - set - { - this["UseJournalQueue"] = value; - } - } - } -} diff --git a/src/NServiceBus.Core/Config/SatelliteConfigurer.cs b/src/NServiceBus.Core/Config/SatelliteConfigurer.cs deleted file mode 100644 index 6f079eb6c9a..00000000000 --- a/src/NServiceBus.Core/Config/SatelliteConfigurer.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NServiceBus.Config -{ - using Satellites; - - class SatelliteConfigurer : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - Configure.ForAllTypes(configuration.Settings.GetAvailableTypes(), t => configuration.RegisterComponents(c => c.ConfigureComponent(t, DependencyLifecycle.SingleInstance))); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Config/TransportConfig.cs b/src/NServiceBus.Core/Config/TransportConfig.cs deleted file mode 100644 index d001e016352..00000000000 --- a/src/NServiceBus.Core/Config/TransportConfig.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus.Config -{ - using System.Configuration; - using Unicast.Transport; - - /// - /// Settings that applies to the transport - /// - public class TransportConfig : ConfigurationSection - { - /// - /// Specifies the maximum concurrency level this is able to support. - /// - [ConfigurationProperty("MaximumConcurrencyLevel", IsRequired = false, DefaultValue = 1)] - public int MaximumConcurrencyLevel - { - get - { - return (int)this["MaximumConcurrencyLevel"]; - } - set - { - this["MaximumConcurrencyLevel"] = value; - - } - } - - /// - /// The maximum number of times to retry processing a message - /// when it fails before moving it to the error queue. - /// - [ConfigurationProperty("MaxRetries", IsRequired = false, DefaultValue = 5)] - public int MaxRetries - { - get - { - return (int)this["MaxRetries"]; - } - set - { - this["MaxRetries"] = value; - } - } - - /// - /// The max throughput for the transport. This allows the user to throttle their endpoint if needed - /// - [ConfigurationProperty("MaximumMessageThroughputPerSecond", IsRequired = false, DefaultValue = 0)] - public int MaximumMessageThroughputPerSecond - { - get - { - return (int)this["MaximumMessageThroughputPerSecond"]; - } - set - { - this["MaximumMessageThroughputPerSecond"] = value; - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Config/UnicastBusConfig.cs b/src/NServiceBus.Core/Config/UnicastBusConfig.cs deleted file mode 100644 index 5faf243de8b..00000000000 --- a/src/NServiceBus.Core/Config/UnicastBusConfig.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace NServiceBus.Config -{ - using System; - using System.Configuration; - - /// - /// A configuration section for UnicastBus specific settings. - /// - public class UnicastBusConfig : ConfigurationSection - { - /// - /// Gets/sets the address for sending control messages to the distributor. - /// - [ConfigurationProperty("DistributorControlAddress", IsRequired = false)] - public string DistributorControlAddress - { - get - { - var result = this["DistributorControlAddress"] as string; - if (string.IsNullOrWhiteSpace(result)) - result = null; - - return result; - } - set - { - this["DistributorControlAddress"] = value; - } - } - - /// - /// Gets/sets the distributor's data address - used as the return address of messages sent by this endpoint. - /// - [ConfigurationProperty("DistributorDataAddress", IsRequired = false)] - public string DistributorDataAddress - { - get - { - var result = this["DistributorDataAddress"] as string; - if (string.IsNullOrWhiteSpace(result)) - result = null; - - return result; - } - set - { - this["DistributorDataAddress"] = value; - } - } - - /// - /// Gets/sets the address to which messages received will be forwarded. - /// - [ConfigurationProperty("ForwardReceivedMessagesTo", IsRequired = false)] - public string ForwardReceivedMessagesTo - { - get - { - var result = this["ForwardReceivedMessagesTo"] as string; - if (string.IsNullOrWhiteSpace(result)) - result = null; - - return result; - } - set - { - this["ForwardReceivedMessagesTo"] = value; - } - } - - - /// - /// Gets/sets the time to be received set on forwarded messages - /// - [ConfigurationProperty("TimeToBeReceivedOnForwardedMessages", IsRequired = false)] - public TimeSpan TimeToBeReceivedOnForwardedMessages - { - get - { - return (TimeSpan)this["TimeToBeReceivedOnForwardedMessages"]; - } - set - { - this["TimeToBeReceivedOnForwardedMessages"] = value; - } - } - - /// - /// Gets/sets the address that the timeout manager will use to send and receive messages. - /// - [ConfigurationProperty("TimeoutManagerAddress", IsRequired = false)] - public string TimeoutManagerAddress - { - get - { - var result = this["TimeoutManagerAddress"] as string; - if (string.IsNullOrWhiteSpace(result)) - result = null; - - return result; - } - set - { - this["TimeoutManagerAddress"] = value; - } - } - - /// - /// Contains the mappings from message types (or groups of them) to endpoints. - /// - [ConfigurationProperty("MessageEndpointMappings", IsRequired = false)] - public MessageEndpointMappingCollection MessageEndpointMappings - { - get - { - return this["MessageEndpointMappings"] as MessageEndpointMappingCollection; - } - set - { - this["MessageEndpointMappings"] = value; - } - } - } -} diff --git a/src/NServiceBus.Core/Config/ConfigurationSource/DefaultConfigurationSource.cs b/src/NServiceBus.Core/ConfigurationSource/DefaultConfigurationSource.cs similarity index 95% rename from src/NServiceBus.Core/Config/ConfigurationSource/DefaultConfigurationSource.cs rename to src/NServiceBus.Core/ConfigurationSource/DefaultConfigurationSource.cs index cddde9a2297..b4971b4170d 100644 --- a/src/NServiceBus.Core/Config/ConfigurationSource/DefaultConfigurationSource.cs +++ b/src/NServiceBus.Core/ConfigurationSource/DefaultConfigurationSource.cs @@ -11,7 +11,9 @@ public class DefaultConfigurationSource : IConfigurationSource T IConfigurationSource.GetConfiguration() { if (!typeof(ConfigurationSection).IsAssignableFrom(typeof(T))) + { throw new ArgumentException("DefaultConfigurationSource only supports .Net ConfigurationSections"); + } return ConfigurationManager.GetSection(typeof(T).Name) as T; } diff --git a/src/NServiceBus.Core/Config/ConfigurationSource/IConfigurationSource.cs b/src/NServiceBus.Core/ConfigurationSource/IConfigurationSource.cs similarity index 89% rename from src/NServiceBus.Core/Config/ConfigurationSource/IConfigurationSource.cs rename to src/NServiceBus.Core/ConfigurationSource/IConfigurationSource.cs index 6b0abb75911..f2f4d0419a3 100644 --- a/src/NServiceBus.Core/Config/ConfigurationSource/IConfigurationSource.cs +++ b/src/NServiceBus.Core/ConfigurationSource/IConfigurationSource.cs @@ -4,7 +4,7 @@ namespace NServiceBus.Config.ConfigurationSource /// Abstraction of a source of configuration data. /// Implement this interface if you want to change the source of all configuration data. /// If you want to change the source of only a specific set of configuration data, - /// implement instead. + /// implement instead. /// public interface IConfigurationSource { @@ -15,7 +15,7 @@ public interface IConfigurationSource } /// - /// Abstraction of a configuration source for a given piece of configuration data. + /// Abstraction of a configuration source for a given piece of configuration data. /// public interface IProvideConfiguration { diff --git a/src/NServiceBus.Core/Configure.cs b/src/NServiceBus.Core/Configure.cs deleted file mode 100644 index 9fd0bc62cc1..00000000000 --- a/src/NServiceBus.Core/Configure.cs +++ /dev/null @@ -1,212 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using NServiceBus.Config; - using NServiceBus.Config.ConfigurationSource; - using NServiceBus.Features; - using NServiceBus.Hosting.Helpers; - using NServiceBus.Logging; - using NServiceBus.ObjectBuilder; - using NServiceBus.ObjectBuilder.Common; - using NServiceBus.Pipeline; - using NServiceBus.Settings; - using NServiceBus.Utils.Reflection; - - /// - /// Central configuration entry point. - /// - public partial class Configure - { - /// - /// Creates a new instance of . - /// - public Configure(SettingsHolder settings, IContainer container, List> registrations, PipelineSettings pipeline) - { - Settings = settings; - this.pipeline = pipeline; - - RegisterContainerAdapter(container); - RunUserRegistrations(registrations); - - configurer.RegisterSingleton(this); - configurer.RegisterSingleton(settings); - } - - /// - /// Provides access to the settings holder - /// - public SettingsHolder Settings { get; private set; } - - /// - /// Gets the builder. - /// - public IBuilder Builder { get; private set; } - - /// - /// Returns types in assemblies found in the current directory. - /// - public IList TypesToScan - { - get { return Settings.GetAvailableTypes(); } - } - - void RunUserRegistrations(List> registrations) - { - foreach (var registration in registrations) - { - registration(configurer); - } - } - - void RegisterContainerAdapter(IContainer container) - { - var b = new CommonObjectBuilder - { - Container = container, - }; - - Builder = b; - configurer = b; - - configurer.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(c => c.Container, container); - } - - void WireUpConfigSectionOverrides() - { - foreach (var t in TypesToScan.Where(t => t.GetInterfaces().Any(IsGenericConfigSource))) - { - configurer.ConfigureComponent(t, DependencyLifecycle.InstancePerCall); - } - } - - /// - /// Returns the queue name of this endpoint. - /// - public Address LocalAddress - { - get - { - Debug.Assert(localAddress != null); - return localAddress; - } - } - - internal void Initialize() - { - WireUpConfigSectionOverrides(); - - featureActivator = new FeatureActivator(Settings); - - configurer.RegisterSingleton(featureActivator); - - ForAllTypes(TypesToScan, t => featureActivator.Add(t.Construct())); - - ForAllTypes(TypesToScan, t => configurer.ConfigureComponent(t, DependencyLifecycle.InstancePerCall)); - - ForAllTypes(TypesToScan, t => configurer.ConfigureComponent(t, DependencyLifecycle.InstancePerCall)); - - ActivateAndInvoke(TypesToScan, t => t.Run(this)); - - var featureStats = featureActivator.SetupFeatures(new FeatureConfigurationContext(this)); - - configurer.RegisterSingleton(featureStats); - - featureActivator.RegisterStartupTasks(configurer); - - localAddress =Settings.LocalAddress(); - - foreach (var o in Builder.BuildAll()) - { - o.Run(this); - } - } - - /// - /// Applies the given action to all the scanned types that can be assigned to . - /// - internal static void ForAllTypes(IEnumerable types, Action action) where T : class - { - // ReSharper disable HeapView.SlowDelegateCreation - foreach (var type in types.Where(t => typeof(T).IsAssignableFrom(t) && !(t.IsAbstract || t.IsInterface))) - { - action(type); - } - // ReSharper restore HeapView.SlowDelegateCreation - } - - internal Address PublicReturnAddress - { - get - { - if (!Settings.HasSetting("PublicReturnAddress")) - { - return LocalAddress; - } - - return Settings.Get
("PublicReturnAddress"); - } - } - - internal static IList GetAllowedTypes(params Assembly[] assemblies) - { - var types = new List(); - Array.ForEach( - assemblies, - a => - { - try - { - types.AddRange(a.GetTypes() - .Where(AssemblyScanner.IsAllowedType)); - } - catch (ReflectionTypeLoadException exception) - { - var errorMessage = AssemblyScanner.FormatReflectionTypeLoadException(a.FullName, exception); - LogManager.GetLogger().Warn(errorMessage); - types.AddRange(exception.Types.Where(AssemblyScanner.IsAllowedType)); - //intentionally swallow exception - } - }); - return types; - } - - internal static void ActivateAndInvoke(IList types, Action action) where T : class - { - ForAllTypes(types, t => - { - var instanceToInvoke = (T)Activator.CreateInstance(t); - action(instanceToInvoke); - }); - } - - static bool IsGenericConfigSource(Type t) - { - if (!t.IsGenericType) - { - return false; - } - - var args = t.GetGenericArguments(); - if (args.Length != 1) - { - return false; - } - - return typeof(IProvideConfiguration<>).MakeGenericType(args).IsAssignableFrom(t); - } - - internal IConfigureComponents configurer; - - FeatureActivator featureActivator; - - internal PipelineSettings pipeline; - - //HACK: Set by the tests - internal Address localAddress; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/ConfigureDefaultBuilder_Obsolete.cs b/src/NServiceBus.Core/ConfigureDefaultBuilder_Obsolete.cs deleted file mode 100644 index 7562f8741bb..00000000000 --- a/src/NServiceBus.Core/ConfigureDefaultBuilder_Obsolete.cs +++ /dev/null @@ -1,24 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Default builder will be used automatically. It is safe to remove this code.")] - public static class ConfigureDefaultBuilder - { - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Default builder will be used automatically. It is safe to remove this code.")] - public static Configure DefaultBuilder(this Configure config) - { - throw new NotImplementedException(); - } - - } -} diff --git a/src/NServiceBus.Core/ConfigureFaultsForwarder_Obsolete.cs b/src/NServiceBus.Core/ConfigureFaultsForwarder_Obsolete.cs deleted file mode 100644 index a283d48680a..00000000000 --- a/src/NServiceBus.Core/ConfigureFaultsForwarder_Obsolete.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0")] - public static class ConfigureFaultsForwarder - { - [ObsoleteEx(Message="It is safe to remove this method call. This is the default behavior.", RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0")] - public static Configure MessageForwardingInCaseOfFault(this Configure config) - { - throw new InvalidOperationException(); - } - } -} diff --git a/src/NServiceBus.Core/ConfigureHandlerSettings.cs b/src/NServiceBus.Core/ConfigureHandlerSettings.cs deleted file mode 100644 index e170b80f698..00000000000 --- a/src/NServiceBus.Core/ConfigureHandlerSettings.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using NServiceBus.ObjectBuilder; - - /// - /// Extension methods for injecting props in . - /// - public static class ConfigureHandlerSettings - { - /// - /// Initializes with the specified properties. - /// - /// The type. - /// The configuration instance. - /// The property name to be injected. - /// The value to assign to the . - public static void InitializeHandlerProperty(this BusConfiguration config, string property, object value) - { - List> list; - if (!config.Settings.TryGet("NServiceBus.HandlerProperties", out list)) - { - list = new List>(); - config.Settings.Set("NServiceBus.HandlerProperties", list); - } - - list.Add(c=> c.ConfigureProperty(property, value)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/ConfigureInMemoryFaultManagement.cs b/src/NServiceBus.Core/ConfigureInMemoryFaultManagement.cs deleted file mode 100644 index d9fa60b3b82..00000000000 --- a/src/NServiceBus.Core/ConfigureInMemoryFaultManagement.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus -{ - using NServiceBus.Features; - - /// - /// Contains extension methods to NServiceBus.Configure - /// - public static partial class ConfigureInMemoryFaultManagement - { - /// - /// Use in-memory fault management. - /// - public static void DiscardFailedMessagesInsteadOfSendingToErrorQueue(this BusConfiguration config) - { - config.EnableFeature(); - config.DisableFeature(); - } - } -} diff --git a/src/NServiceBus.Core/ConfigureInMemoryFaultManagement_obsolete.cs b/src/NServiceBus.Core/ConfigureInMemoryFaultManagement_obsolete.cs deleted file mode 100644 index 865a782cc20..00000000000 --- a/src/NServiceBus.Core/ConfigureInMemoryFaultManagement_obsolete.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Contains extension methods to NServiceBus.Configure - /// - public static partial class ConfigureInMemoryFaultManagement - { - /// - /// Use in-memory fault management. - /// - [ObsoleteEx( - Message = "Use `configuration.DiscardFailedMessagesInsteadOfSendingToErrorQueue()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - // ReSharper disable UnusedParameter.Global - public static Configure InMemoryFaultManagement(this Configure config) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/ConfigureQueueCreation.cs b/src/NServiceBus.Core/ConfigureQueueCreation.cs index 49ea21f6e3d..da089c21f78 100644 --- a/src/NServiceBus.Core/ConfigureQueueCreation.cs +++ b/src/NServiceBus.Core/ConfigureQueueCreation.cs @@ -1,29 +1,43 @@ namespace NServiceBus { + using System; + using Settings; + /// - /// Contains extension methods for that expose Queue creation settings. + /// Contains extension methods for that expose Queue creation settings. /// - public static partial class ConfigureQueueCreation + public static class ConfigureQueueCreation { /// /// If queues configured do not exist, will cause them not to be created on startup. /// - public static void DoNotCreateQueues(this BusConfiguration config) + /// The instance to apply the settings to. + public static void DoNotCreateQueues(this EndpointConfiguration config) { + Guard.AgainstNull(nameof(config), config); config.Settings.Set("Transport.CreateQueues", false); } /// /// Gets whether or not queues should be created. /// + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "CreateQueues")] public static bool CreateQueues(this Configure config) { + throw new NotImplementedException(); + } + + /// + /// Gets whether or not queues should be created. + /// + public static bool CreateQueues(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); bool createQueues; - if (config.Settings.TryGet("Transport.CreateQueues", out createQueues)) - { - return createQueues; - } - return true; + return !settings.TryGet("Transport.CreateQueues", out createQueues) || createQueues; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ConfigureQueueCreation_Obsolete.cs b/src/NServiceBus.Core/ConfigureQueueCreation_Obsolete.cs deleted file mode 100644 index 27654a3a1dc..00000000000 --- a/src/NServiceBus.Core/ConfigureQueueCreation_Obsolete.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigureQueueCreation - { - - [ObsoleteEx( - Message = "Use `configuration.DoNotCreateQueues()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure DoNotCreateQueues(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0")] - public static bool DontCreateQueues - { - get - { - throw new NotImplementedException(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Configure_Obsolete.cs b/src/NServiceBus.Core/Configure_Obsolete.cs deleted file mode 100644 index 90d0866cb85..00000000000 --- a/src/NServiceBus.Core/Configure_Obsolete.cs +++ /dev/null @@ -1,298 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Reflection; - using Config.ConfigurationSource; - using Features; - using ObjectBuilder; - using Settings; - - public partial class Configure - { - /// - /// Gets/sets the object used to configure components. - /// This object should eventually reference the same container as the Builder. - /// - [ObsoleteEx( - Message = "Use `configuration.RegisterComponents(c => c.ConfigureComponent... ))`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public IConfigureComponents Configurer - { - get - { - throw new InvalidOperationException(); - } - // ReSharper disable ValueParameterNotUsed - set - // ReSharper restore ValueParameterNotUsed - { - } - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5.0", - Message = "Configure is now instance based. For usages before the container is configured an instance of `Configure` is passed in. For usages after the container is configured then an instance of `Configure` can be extracted from the container.")] - public static Configure Instance - { - get - { - throw new NotImplementedException(); - } - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.EndpointName('MyEndpoint')`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static string EndpointName - { - get { throw new NotImplementedException(); } - } - - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static bool WithHasBeenCalled() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Simply execute this action instead of calling this method")] - public Configure RunCustomAction(Action action) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Not needed, can safely be removed")] - public IStartableBus CreateBus() - { - Initialize(); - - return Builder.Build(); - } - - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static bool BuilderIsConfigured() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "ReadOnlySettings.GetConfigSection")] - public static T GetConfigSection() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.ConfigureComponent")] - public static IComponentConfig Component(Type type, DependencyLifecycle lifecycle) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.ConfigureComponent")] - public static IComponentConfig Component(DependencyLifecycle lifecycle) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.ConfigureComponent")] - public static IComponentConfig Component(Func componentFactory, DependencyLifecycle lifecycle) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.ConfigureComponent")] - public static IComponentConfig Component(Func componentFactory, DependencyLifecycle lifecycle) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.HasComponent")] - public static bool HasComponent() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Configure is now instance based.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "configure.Configurer.HasComponent")] - public static bool HasComponent(Type componentType) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.CustomConfigurationSource(myConfigSource)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public Configure CustomConfigurationSource(IConfigurationSource configurationSource) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "Bus.Create(new BusConfiguration())")] - public static Configure With() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.ScanAssembliesInDirectory(directoryToProbe)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure With(string probeDirectory) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.AssembliesToScan(listOfAssemblies)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure With(IEnumerable assemblies) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.AssembliesToScan(listOfAssemblies)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure With(params Assembly[] assemblies) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.TypesToScan(listOfTypes)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure With(IEnumerable typesToScan) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefineEndpointName(Func definesEndpointName) - { - throw new NotImplementedException(); - } - - /// - /// Sets the function that specified the name of this endpoint - /// - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefineEndpointName(string name) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "No longer an extension point for NSB")] - public static Func LoadAssembly = s => Assembly.LoadFrom(s.FullName); - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Func GetEndpointNameAction; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UseSerialization())`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static SerializationSettings Serialization - { - get { throw new NotImplementedException(); } - } - - [ObsoleteEx( - Message = "This has been converted to extension methods. Use `configuration.EnableFeature()` or `configuration.DisableFeature()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static FeatureSettings Features - { - get - { - throw new NotImplementedException(); - } - } - - [ObsoleteEx( - Message = "This has been converted to an extension method. Use `configuration.Transactions()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static TransactionSettings Transactions - { - get - { - throw new NotImplementedException(); - } - } - } -} - -namespace NServiceBus.Features -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.EnableFeature()` or `configuration.DisableFeature()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public class FeatureSettings - { - } - -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Container/ContainerCustomizations.cs b/src/NServiceBus.Core/Container/ContainerCustomizations.cs index 90ac2b86b99..4d1b0d9a853 100644 --- a/src/NServiceBus.Core/Container/ContainerCustomizations.cs +++ b/src/NServiceBus.Core/Container/ContainerCustomizations.cs @@ -3,7 +3,7 @@ namespace NServiceBus.Container using Settings; /// - /// Container customization. + /// Container customization. /// public class ContainerCustomizations { @@ -13,7 +13,7 @@ internal ContainerCustomizations(SettingsHolder settings) } /// - /// The settings instance to use to store an existing container instance. + /// The settings instance to use to store an existing container instance. /// public SettingsHolder Settings { get; private set; } } diff --git a/src/NServiceBus.Core/Container/ContainerDefinition.cs b/src/NServiceBus.Core/Container/ContainerDefinition.cs index cbf00190d56..2f7cccd32eb 100644 --- a/src/NServiceBus.Core/Container/ContainerDefinition.cs +++ b/src/NServiceBus.Core/Container/ContainerDefinition.cs @@ -4,12 +4,12 @@ namespace NServiceBus.Container using Settings; /// - /// Base class for container definitions. + /// Base class for container definitions. /// public abstract class ContainerDefinition { /// - /// Implementers need to new up a new container. + /// Implementers need to new up a new container. /// /// The settings to check if an existing container exists. /// The new container wrapper. diff --git a/src/NServiceBus.Core/ContentTypes.cs b/src/NServiceBus.Core/ContentTypes.cs index 43657dbc9ae..c7f0ab9488f 100644 --- a/src/NServiceBus.Core/ContentTypes.cs +++ b/src/NServiceBus.Core/ContentTypes.cs @@ -6,22 +6,12 @@ public static class ContentTypes { /// - /// Indicates that the content type is "application/bson" - /// - public const string Bson = "application/bson"; - - /// - /// Indicates that the content type is "application/binary" - /// - public const string Binary = "application/binary"; - - /// - /// Indicates that the content type is "application/json" + /// Indicates that the content type is "application/json". /// public const string Json = "application/json"; /// - /// Indicates that the content type is "text/xml" + /// Indicates that the content type is "text/xml". /// public const string Xml = "text/xml"; } diff --git a/src/NServiceBus.Core/Conventions.cs b/src/NServiceBus.Core/Conventions.cs index cf3055ef0fe..df7b02b8669 100644 --- a/src/NServiceBus.Core/Conventions.cs +++ b/src/NServiceBus.Core/Conventions.cs @@ -5,55 +5,47 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; - using NServiceBus.Utils.Reflection; /// - /// Message convention definitions. + /// Message convention definitions. /// - public class Conventions + public partial class Conventions { - internal IEnumerable GetDataBusProperties(object message) + internal List GetDataBusProperties(object message) { - var messageType = message.GetType(); - - - List value; - - if (!cache.TryGetValue(messageType, out value)) + return cache.GetOrAdd(message.GetType(), messageType => { - value = messageType.GetProperties() - .Where(IsDataBusProperty) - .Select(property => new DataBusPropertyInfo + var properties = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var propertyInfo in messageType.GetProperties()) + { + if (IsDataBusProperty(propertyInfo)) { - Name = property.Name, - Getter = DelegateFactory.CreateGet(property), - Setter = DelegateFactory.CreateSet(property), - }).ToList(); - - cache[messageType] = value; - } - - return value; - } - - /// - /// Returns the time to be received for a give . - /// - public TimeSpan GetTimeToBeReceived(Type messageType) - { - return TimeToBeReceivedAction(messageType); + properties.Add(new DataBusPropertyInfo + { + Name = propertyInfo.Name, + Getter = DelegateFactory.CreateGet(propertyInfo), + Setter = DelegateFactory.CreateSet(propertyInfo) + }); + } + } + return properties; + }); } /// - /// Returns true if the given type is a message type. + /// Returns true if the given type is a message type. /// public bool IsMessageType(Type t) { + Guard.AgainstNull(nameof(t), t); try { return MessagesConventionCache.ApplyConvention(t, - type => + typeHandle => { + var type = Type.GetTypeFromHandle(typeHandle); + if (IsInSystemConventionList(type)) { return true; @@ -74,19 +66,21 @@ public bool IsMessageType(Type t) } /// - /// Returns true is message is a system message type. + /// Returns true is message is a system message type. /// public bool IsInSystemConventionList(Type t) { + Guard.AgainstNull(nameof(t), t); return IsSystemMessageActions.Any(isSystemMessageAction => isSystemMessageAction(t)); } /// - /// Add system message convention + /// Add system message convention. /// - /// Function to define system message convention + /// Function to define system message convention. public void AddSystemMessagesConventions(Func definesMessageType) { + Guard.AgainstNull(nameof(definesMessageType), definesMessageType); if (!IsSystemMessageActions.Contains(definesMessageType)) { IsSystemMessageActions.Add(definesMessageType); @@ -95,14 +89,16 @@ public void AddSystemMessagesConventions(Func definesMessageType) } /// - /// Returns true if the given type is a command type. + /// Returns true if the given type is a command type. /// public bool IsCommandType(Type t) { + Guard.AgainstNull(nameof(t), t); try { - return CommandsConventionCache.ApplyConvention(t, type => + return CommandsConventionCache.ApplyConvention(t, typeHandle => { + var type = Type.GetTypeFromHandle(typeHandle); if (type.IsFromParticularAssembly()) { return false; @@ -116,33 +112,13 @@ public bool IsCommandType(Type t) } } - /// - /// Returns true if the given type should not be written to disk when sent. - /// - public bool IsExpressMessageType(Type t) - { - try - { - return ExpressConventionCache.ApplyConvention(t, type => - { - if (type.IsFromParticularAssembly()) - { - return false; - } - return IsExpressMessageAction(type); - }); - } - catch (Exception ex) - { - throw new Exception("Failed to evaluate Express convention. See inner exception for details.", ex); - } - } /// - /// Returns true if the given property should be encrypted + /// Returns true if the given property should be encrypted. /// public bool IsEncryptedProperty(PropertyInfo property) { + Guard.AgainstNull(nameof(property), property); try { //the message mutator will cache the whole message so we don't need to cache here @@ -155,10 +131,11 @@ public bool IsEncryptedProperty(PropertyInfo property) } /// - /// Returns true if the given property should be send via the DataBus + /// Returns true if the given property should be send via the DataBus. /// public bool IsDataBusProperty(PropertyInfo property) { + Guard.AgainstNull(nameof(property), property); try { return IsDataBusPropertyAction(property); @@ -170,14 +147,16 @@ public bool IsDataBusProperty(PropertyInfo property) } /// - /// Returns true if the given type is a event type. + /// Returns true if the given type is a event type. /// public bool IsEventType(Type t) { + Guard.AgainstNull(nameof(t), t); try { - return EventsConventionCache.ApplyConvention(t, type => + return EventsConventionCache.ApplyConvention(t, typeHandle => { + var type = Type.GetTypeFromHandle(typeHandle); if (type.IsFromParticularAssembly()) { return false; @@ -191,11 +170,10 @@ public bool IsEventType(Type t) } } - readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + ConcurrentDictionary> cache = new ConcurrentDictionary>(); ConventionCache CommandsConventionCache = new ConventionCache(); ConventionCache EventsConventionCache = new ConventionCache(); - ConventionCache ExpressConventionCache = new ConventionCache(); internal Func IsCommandTypeAction = t => typeof(ICommand).IsAssignableFrom(t) && typeof(ICommand) != t; @@ -205,8 +183,6 @@ public bool IsEventType(Type t) internal Func IsEventTypeAction = t => typeof(IEvent).IsAssignableFrom(t) && typeof(IEvent) != t; - internal Func IsExpressMessageAction = t => t.GetCustomAttributes(typeof(ExpressAttribute), true) - .Any(); internal Func IsMessageTypeAction = t => typeof(IMessage).IsAssignableFrom(t) && typeof(IMessage) != t && @@ -216,20 +192,12 @@ public bool IsEventType(Type t) List> IsSystemMessageActions = new List>(); ConventionCache MessagesConventionCache = new ConventionCache(); - internal Func TimeToBeReceivedAction = t => - { - var attributes = t.GetCustomAttributes(typeof(TimeToBeReceivedAttribute), true) - .Select(s => s as TimeToBeReceivedAttribute) - .ToList(); - - return attributes.Count > 0 ? attributes.Last().TimeToBeReceived : TimeSpan.MaxValue; - }; class ConventionCache { - public bool ApplyConvention(Type type, Func action) + public bool ApplyConvention(Type type, Func action) { - return cache.GetOrAdd(type, action); + return cache.GetOrAdd(type.TypeHandle, action); } public void Reset() @@ -237,7 +205,7 @@ public void Reset() cache.Clear(); } - ConcurrentDictionary cache = new ConcurrentDictionary(); + ConcurrentDictionary cache = new ConcurrentDictionary(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/ConventionsBuilder.cs b/src/NServiceBus.Core/ConventionsBuilder.cs index 304de2f1cc0..afbbbc08a5c 100644 --- a/src/NServiceBus.Core/ConventionsBuilder.cs +++ b/src/NServiceBus.Core/ConventionsBuilder.cs @@ -2,75 +2,76 @@ namespace NServiceBus { using System; using System.Reflection; + using Configuration.AdvanceExtensibility; + using Settings; /// - /// Conventions builder class. + /// Defines custom message conventions instead of using the , or interfaces, and other conventions. /// - public class ConventionsBuilder + public class ConventionsBuilder : ExposeSettings { /// - /// Sets the function to be used to evaluate whether a type is a message. + /// Creates a new instance of ConventionsBuilder class. + /// + /// An instance of the current settings. + public ConventionsBuilder(SettingsHolder settings) : base(settings) + { + } + + /// + /// Sets the function to be used to evaluate whether a type is a message. /// public ConventionsBuilder DefiningMessagesAs(Func definesMessageType) { + Guard.AgainstNull(nameof(definesMessageType), definesMessageType); Conventions.IsMessageTypeAction = definesMessageType; return this; } /// - /// Sets the function to be used to evaluate whether a type is a commands. + /// Sets the function to be used to evaluate whether a type is a commands. /// public ConventionsBuilder DefiningCommandsAs(Func definesCommandType) { + Guard.AgainstNull(nameof(definesCommandType), definesCommandType); Conventions.IsCommandTypeAction = definesCommandType; return this; } /// - /// Sets the function to be used to evaluate whether a type is a event. + /// Sets the function to be used to evaluate whether a type is a event. /// public ConventionsBuilder DefiningEventsAs(Func definesEventType) { + Guard.AgainstNull(nameof(definesEventType), definesEventType); Conventions.IsEventTypeAction = definesEventType; return this; } /// - /// Sets the function to be used to evaluate whether a property should be encrypted or not. + /// Sets the function to be used to evaluate whether a property should be encrypted or not. /// public ConventionsBuilder DefiningEncryptedPropertiesAs(Func definesEncryptedProperty) { + Guard.AgainstNull(nameof(definesEncryptedProperty), definesEncryptedProperty); Conventions.IsEncryptedPropertyAction = definesEncryptedProperty; return this; } /// - /// Sets the function to be used to evaluate whether a property should be sent via the DataBus or not. + /// Sets the function to be used to evaluate whether a property should be sent via the DataBus or not. /// public ConventionsBuilder DefiningDataBusPropertiesAs(Func definesDataBusProperty) { + Guard.AgainstNull(nameof(definesDataBusProperty), definesDataBusProperty); Conventions.IsDataBusPropertyAction = definesDataBusProperty; return this; } - /// - /// Sets the function to be used to evaluate whether a message has a time to be received. - /// - public ConventionsBuilder DefiningTimeToBeReceivedAs(Func retrieveTimeToBeReceived) - { - Conventions.TimeToBeReceivedAction = retrieveTimeToBeReceived; - return this; - } /// - /// Sets the function to be used to evaluate whether a type is an express message or not. + /// The defined . /// - public ConventionsBuilder DefiningExpressMessagesAs(Func definesExpressMessageType) - { - Conventions.IsExpressMessageAction = definesExpressMessageType; - return this; - } - - internal Conventions Conventions = new Conventions(); + public Conventions Conventions { get; } = new Conventions(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Correlation/AttachCorrelationIdBehavior.cs b/src/NServiceBus.Core/Correlation/AttachCorrelationIdBehavior.cs new file mode 100644 index 00000000000..7f8798d7cc0 --- /dev/null +++ b/src/NServiceBus.Core/Correlation/AttachCorrelationIdBehavior.cs @@ -0,0 +1,51 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class AttachCorrelationIdBehavior : IBehavior + { + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + var correlationId = context.Extensions.GetOrCreate().CustomCorrelationId; + + //if we don't have a explicit correlation id set + if (string.IsNullOrEmpty(correlationId)) + { + IncomingMessage current; + + //try to get it from the incoming message + if (context.TryGetIncomingPhysicalMessage(out current)) + { + string incomingCorrelationId; + + if (current.Headers.TryGetValue(Headers.CorrelationId, out incomingCorrelationId)) + { + correlationId = incomingCorrelationId; + } + + if (string.IsNullOrEmpty(correlationId) && current.Headers.TryGetValue(Headers.MessageId, out incomingCorrelationId)) + { + correlationId = incomingCorrelationId; + } + } + } + + //if we still doesn't have one we'll use the message id + if (string.IsNullOrEmpty(correlationId)) + { + correlationId = context.MessageId; + } + + context.Headers[Headers.CorrelationId] = correlationId; + return next(context); + } + + public class State + { + public string CustomCorrelationId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Correlation/CorrelationContextExtensions.cs b/src/NServiceBus.Core/Correlation/CorrelationContextExtensions.cs new file mode 100644 index 00000000000..823619da639 --- /dev/null +++ b/src/NServiceBus.Core/Correlation/CorrelationContextExtensions.cs @@ -0,0 +1,68 @@ +namespace NServiceBus +{ + /// + /// Extension methods for manipulating the message Correlation Id. + /// + public static class CorrelationContextExtensions + { + /// + /// Allows users to set a custom correlation id. + /// + /// Options being extended. + /// The custom correlation id. + public static void SetCorrelationId(this SendOptions options, string correlationId) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNullAndEmpty(nameof(correlationId), correlationId); + + options.Context.GetOrCreate() + .CustomCorrelationId = correlationId; + } + + /// + /// Allows users to set a custom correlation id. + /// + /// Options being extended. + /// The custom correlation id. + public static void SetCorrelationId(this ReplyOptions options, string correlationId) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNullAndEmpty(nameof(correlationId), correlationId); + + options.Context.GetOrCreate() + .CustomCorrelationId = correlationId; + } + + /// + /// Retrieves the correlation id specified by the user by using + /// . + /// + /// Options being extended. + /// The configured correlation id or null when no correlation id was configured. + public static string GetCorrelationId(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + AttachCorrelationIdBehavior.State state; + options.Context.TryGet(out state); + + return state?.CustomCorrelationId; + } + + /// + /// Retrieves the correlation id specified by the user by using + /// . + /// + /// Options being extended. + /// The configured correlation id or null when no correlation id was configured. + public static string GetCorrelationId(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + AttachCorrelationIdBehavior.State state; + options.Context.TryGet(out state); + + return state?.CustomCorrelationId; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Correlation/MessageCorrelation.cs b/src/NServiceBus.Core/Correlation/MessageCorrelation.cs new file mode 100644 index 00000000000..c6c05c95b0b --- /dev/null +++ b/src/NServiceBus.Core/Correlation/MessageCorrelation.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Features +{ + class MessageCorrelation : Feature + { + public MessageCorrelation() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Pipeline.Register("AttachCorrelationId", new AttachCorrelationIdBehavior(), "Makes sure that outgoing messages have a correlation id header set"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction.cs b/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction.cs index b8e7a9b2783..90b7aec3412 100644 --- a/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction.cs +++ b/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction.cs @@ -1,23 +1,23 @@ namespace NServiceBus { using System; - + using System.Threading.Tasks; /// - /// Allow override critical error action + /// Allow override critical error action. /// public static partial class ConfigureCriticalErrorAction { - /// - /// Sets the function to be used when critical error occurs. + /// Sets the function to be used when critical error occurs. /// - /// The to extend. + /// The to extend. /// Assigns the action to perform on critical error. - public static void DefineCriticalErrorAction(this BusConfiguration busConfiguration, Action onCriticalError) + public static void DefineCriticalErrorAction(this EndpointConfiguration endpointConfiguration, Func onCriticalError) { - busConfiguration.Settings.Set("onCriticalErrorAction", onCriticalError); + Guard.AgainstNull(nameof(endpointConfiguration), endpointConfiguration); + Guard.AgainstNull(nameof(onCriticalError), onCriticalError); + endpointConfiguration.Settings.Set("onCriticalErrorAction", onCriticalError); } - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction_Obsolete.cs b/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction_Obsolete.cs deleted file mode 100644 index b6335fea7ac..00000000000 --- a/src/NServiceBus.Core/CriticalError/ConfigureCriticalErrorAction_Obsolete.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigureCriticalErrorAction - { - - [ObsoleteEx( - Replacement = "ConfigureCriticalErrorAction.DefineCriticalErrorAction()", - Message = "Use `configuration.DefineCriticalErrorAction()`, where configuration is an instance of type `BusConfiguration`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure DefineCriticalErrorAction(this Configure config, Action onCriticalError) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Inject an instace of `CriticalError` and call `CriticalError.Raise`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static void RaiseCriticalError(string errorMessage, Exception exception) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/CriticalError.cs b/src/NServiceBus.Core/CriticalError/CriticalError.cs index 47670ce7f59..62644b84e9a 100644 --- a/src/NServiceBus.Core/CriticalError/CriticalError.cs +++ b/src/NServiceBus.Core/CriticalError/CriticalError.cs @@ -1,63 +1,100 @@ namespace NServiceBus { using System; - using System.Threading; - using NServiceBus.Logging; - using NServiceBus.ObjectBuilder; + using System.Collections.Generic; + using System.Threading.Tasks; + using Logging; /// - /// A holder for that exposes access to the action defined by . + /// A holder for that exposes access to the action defined by . /// /// /// Call to trigger the action. /// public class CriticalError { - Action onCriticalErrorAction; - Configure configure; - /// - /// Creates an instance of + /// Initializes a new instance of . /// /// The action to execute when a critical error is triggered. - /// The instance. - public CriticalError(Action onCriticalErrorAction, Configure configure) + public CriticalError(Func onCriticalErrorAction) { - if (configure == null) + if (onCriticalErrorAction == null) { - throw new ArgumentNullException("configure"); + criticalErrorAction = DefaultCriticalErrorHandling; } - this.onCriticalErrorAction = onCriticalErrorAction; - this.configure = configure; - } - - void DefaultCriticalErrorHandling() - { - var components = configure.Builder.Build(); - if (!components.HasComponent()) + else { - return; + criticalErrorAction = onCriticalErrorAction; } + } - configure.Builder.Build() - .Dispose(); + static Task DefaultCriticalErrorHandling(ICriticalErrorContext criticalErrorContext) + { + return criticalErrorContext.Stop(); } /// - /// Trigger the action defined by . + /// Trigger the action defined by + /// + /// . /// - public void Raise(string errorMessage, Exception exception) + public virtual void Raise(string errorMessage, Exception exception) { + Guard.AgainstNullAndEmpty(nameof(errorMessage), errorMessage); + Guard.AgainstNull(nameof(exception), exception); LogManager.GetLogger("NServiceBus").Fatal(errorMessage, exception); - if (onCriticalErrorAction == null) + lock (endpointCriticalLock) { - ThreadPool.QueueUserWorkItem(state => DefaultCriticalErrorHandling()); + if (endpoint == null) + { + criticalErrors.Add(new LatentCritical + { + Message = errorMessage, + Exception = exception + }); + return; + } } - else + + // don't await the criticalErrorAction in order to avoid deadlocks + RaiseForEndpoint(errorMessage, exception); + } + + void RaiseForEndpoint(string errorMessage, Exception exception) + { + Task.Run(() => + { + var context = new CriticalErrorContext(endpoint.Stop, errorMessage, exception); + return criticalErrorAction(context); + }); + } + + internal void SetEndpoint(IEndpointInstance endpointInstance) + { + lock (endpointCriticalLock) { - ThreadPool.QueueUserWorkItem(state => onCriticalErrorAction(errorMessage, exception)); + endpoint = endpointInstance; + foreach (var latentCritical in criticalErrors) + { + RaiseForEndpoint(latentCritical.Message, latentCritical.Exception); + } + criticalErrors.Clear(); } } + + Func criticalErrorAction; + + List criticalErrors = new List(); + IEndpointInstance endpoint; + object endpointCriticalLock = new object(); + + class LatentCritical + { + public string Message { get; set; } + public Exception Exception { get; set; } + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/CriticalErrorContext.cs b/src/NServiceBus.Core/CriticalError/CriticalErrorContext.cs new file mode 100644 index 00000000000..f13b6428025 --- /dev/null +++ b/src/NServiceBus.Core/CriticalError/CriticalErrorContext.cs @@ -0,0 +1,42 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// See . + /// + public class CriticalErrorContext : ICriticalErrorContext + { + /// + /// Initializes a new instance of . + /// + /// See . + /// See . + /// See . + public CriticalErrorContext(Func stop, string error, Exception exception) + { + Guard.AgainstNull(nameof(stop), stop); + Guard.AgainstNullAndEmpty(nameof(error), error); + Guard.AgainstNull(nameof(exception), exception); + Stop = stop; + Error = error; + Exception = exception; + } + + /// + /// See . + /// + public Func Stop { get; } + + /// + /// See . + /// + public string Error { get; } + + /// + /// See . + /// + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/CriticalErrorHandling.cs b/src/NServiceBus.Core/CriticalError/CriticalErrorHandling.cs deleted file mode 100644 index 886a6be8b71..00000000000 --- a/src/NServiceBus.Core/CriticalError/CriticalErrorHandling.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - - /// - /// Controls what happens when a critical error occurs. - /// - class CriticalErrorHandling : Feature - { - /// - /// Initializes a enw instance of . - /// - internal CriticalErrorHandling() - { - EnableByDefault(); - } - - - /// - /// . - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - Action errorAction; - context.Settings.TryGet("onCriticalErrorAction", out errorAction); - context.Container.ConfigureComponent(builder => new CriticalError(errorAction, builder.Build()), DependencyLifecycle.SingleInstance); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/CriticalError/ICriticalErrorContext.cs b/src/NServiceBus.Core/CriticalError/ICriticalErrorContext.cs new file mode 100644 index 00000000000..a593d62ed98 --- /dev/null +++ b/src/NServiceBus.Core/CriticalError/ICriticalErrorContext.cs @@ -0,0 +1,26 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// The context of a critical error handler used by . + /// + public interface ICriticalErrorContext + { + /// + /// A delegate that optionally stops the endpoint. By default this is a pointer . + /// + Func Stop { get; } + + /// + /// A description of the error. + /// + string Error { get; } + + /// + /// The last that cause the error. + /// + Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus.cs b/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus.cs index 54ab5c89e5a..91b73e1180b 100644 --- a/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus.cs +++ b/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus.cs @@ -1,39 +1,25 @@ namespace NServiceBus { - using NServiceBus.DataBus; + using DataBus; /// - /// Contains extension methods to for the file share data bus + /// Contains extension methods to for the file share data bus. /// public static partial class ConfigureFileShareDataBus { - /// - /// Use the file-based databus implementation with the default binary serializer. - /// - /// The configuration. - /// The location to which to write/read serialized properties for the databus. - /// The configuration. - [ObsoleteEx( - Message = "Use `configuration.UseDataBus().BasePath(basePath)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.5")] - public static void FileShareDataBus(this BusConfiguration config, string basePath) - { - config.Settings.Set("FileShareDataBusPath", basePath); - } - /// /// The location to which to write/read serialized properties for the databus. /// /// The configuration object. /// The location to which to write/read serialized properties for the databus. /// The configuration. - public static DataBusExtentions BasePath(this DataBusExtentions config, string basePath) + public static DataBusExtensions BasePath(this DataBusExtensions config, string basePath) { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(basePath), basePath); config.Settings.Set("FileShareDataBusPath", basePath); return config; } } - -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus_Obsolete.cs b/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus_Obsolete.cs deleted file mode 100644 index f4583435604..00000000000 --- a/src/NServiceBus.Core/DataBus/ConfigureFileShareDataBus_Obsolete.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigureFileShareDataBus - { - - [ObsoleteEx( - Replacement = "ConfigureFileShareDataBus.FileShareDataBus(this BusConfiguration config, string basePath)", - Message = "Use `configuration.FileShareDataBus(basePath)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure FileShareDataBus(this Configure config, string basePath) - { - throw new NotImplementedException(); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/CustomDataBus.cs b/src/NServiceBus.Core/DataBus/CustomDataBus.cs index ae6b5b92431..693500e6aae 100644 --- a/src/NServiceBus.Core/DataBus/CustomDataBus.cs +++ b/src/NServiceBus.Core/DataBus/CustomDataBus.cs @@ -1,13 +1,14 @@ namespace NServiceBus { using System; - using NServiceBus.DataBus; + using DataBus; + using Features; class CustomDataBus : DataBusDefinition { protected internal override Type ProvidedByFeature() { - return typeof(Features.CustomIDataBus); + return typeof(CustomIDataBus); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/CustomIDataBus.cs b/src/NServiceBus.Core/DataBus/CustomIDataBus.cs index 53198ebcdeb..fe414640639 100644 --- a/src/NServiceBus.Core/DataBus/CustomIDataBus.cs +++ b/src/NServiceBus.Core/DataBus/CustomIDataBus.cs @@ -14,4 +14,4 @@ protected internal override void Setup(FeatureConfigurationContext context) context.Container.ConfigureComponent(context.Settings.Get("CustomDataBusType"), DependencyLifecycle.SingleInstance); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBus.cs b/src/NServiceBus.Core/DataBus/DataBus.cs index 7b5e88e7c62..ef6c36df54f 100644 --- a/src/NServiceBus.Core/DataBus/DataBus.cs +++ b/src/NServiceBus.Core/DataBus/DataBus.cs @@ -1,24 +1,18 @@ namespace NServiceBus.Features { using System; - using System.Linq; + using System.Threading.Tasks; using NServiceBus.DataBus; - using NServiceBus.Settings; + using Settings; /// - /// Used to configure the databus. + /// Used to configure the databus. /// public class DataBus : Feature { internal DataBus() { - EnableByDefault(); - - Prerequisite(DataBusPropertiesFound, "No databus properties was found in available messages"); - Defaults(s => s.EnableFeatureByDefault(GetSelectedFeatureForDataBus(s))); - - RegisterStartupTask(); } static Type GetSelectedFeatureForDataBus(SettingsHolder settings) @@ -33,18 +27,8 @@ static Type GetSelectedFeatureForDataBus(SettingsHolder settings) return dataBusDefinition.ProvidedByFeature(); } - class IDataBusInitializer : FeatureStartupTask - { - public IDataBus DataBus { get; set; } - - protected override void OnStart() - { - DataBus.Start(); - } - } - /// - /// Called when the features is activated + /// Called when the features is activated. /// protected internal override void Setup(FeatureConfigurationContext context) { @@ -53,46 +37,27 @@ protected internal override void Setup(FeatureConfigurationContext context) context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); } - context.Pipeline.Register(); - context.Pipeline.Register(); + context.RegisterStartupTask(b => b.Build()); + context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + + var conventions = context.Settings.Get(); + context.Pipeline.Register(new DataBusReceiveBehavior.Registration(conventions)); + context.Pipeline.Register(new DataBusSendBehavior.Registration(conventions)); } - static bool DataBusPropertiesFound(FeatureConfigurationContext context) + class IDataBusInitializer : FeatureStartupTask { - var dataBusPropertyFound = false; - var conventions = context.Settings.Get(); + public IDataBus DataBus { get; set; } - if (!context.Container.HasComponent() && System.Diagnostics.Debugger.IsAttached) + protected override Task OnStart(IMessageSession session) { - var properties = context.Settings.GetAvailableTypes() - .Where(conventions.IsMessageType) - .SelectMany(messageType => messageType.GetProperties()) - .Where(conventions.IsDataBusProperty); - - foreach (var property in properties) - { - dataBusPropertyFound = true; - - if (!property.PropertyType.IsSerializable) - { - throw new InvalidOperationException( - String.Format( - @"The property type for '{0}' is not serializable. -In order to use the databus feature for transporting the data stored in the property types defined in the call '.DefiningDataBusPropertiesAs()', need to be serializable. -To fix this, please mark the property type '{0}' as serializable, see http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx on how to do this.", - String.Format("{0}.{1}", property.DeclaringType.FullName, property.Name))); - } - } + return DataBus.Start(); } - else + + protected override Task OnStop(IMessageSession session) { - dataBusPropertyFound = context.Settings.GetAvailableTypes() - .Where(conventions.IsMessageType) - .SelectMany(messageType => messageType.GetProperties()) - .Any(conventions.IsDataBusProperty); + return TaskEx.CompletedTask; } - - return dataBusPropertyFound; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBusDefinition.cs b/src/NServiceBus.Core/DataBus/DataBusDefinition.cs index aa81288f7c2..f290ab82552 100644 --- a/src/NServiceBus.Core/DataBus/DataBusDefinition.cs +++ b/src/NServiceBus.Core/DataBus/DataBusDefinition.cs @@ -3,12 +3,12 @@ using System; /// - /// Defines a databus that can be used by NServiceBus + /// Defines a databus that can be used by NServiceBus. /// public abstract class DataBusDefinition { /// - /// The feature to enable when this databus is selected + /// The feature to enable when this databus is selected. /// protected internal abstract Type ProvidedByFeature(); } diff --git a/src/NServiceBus.Core/DataBus/DataBusExtensions.cs b/src/NServiceBus.Core/DataBus/DataBusExtensions.cs new file mode 100644 index 00000000000..e96e870dcba --- /dev/null +++ b/src/NServiceBus.Core/DataBus/DataBusExtensions.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.DataBus +{ + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// This class provides implementers of databus with an extension mechanism for custom settings via extension methods. + /// + /// The databus definition eg . + public class DataBusExtensions : DataBusExtensions where T : DataBusDefinition + { + /// + /// Default constructor. + /// + public DataBusExtensions(SettingsHolder settings) + : base(settings) + { + } + } + + /// + /// This class provides implementers of databus with an extension mechanism for custom settings via extension methods. + /// + public class DataBusExtensions : ExposeSettings + { + /// + /// Default constructor. + /// + public DataBusExtensions(SettingsHolder settings) + : base(settings) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBusExtentions.cs b/src/NServiceBus.Core/DataBus/DataBusExtentions.cs deleted file mode 100644 index 65fbaa9d925..00000000000 --- a/src/NServiceBus.Core/DataBus/DataBusExtentions.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus.DataBus -{ - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Settings; - - /// - /// This class provides implementers of databus with an extension mechanism for custom settings via extention methods. - /// - /// The databus definition eg . - public class DataBusExtentions : DataBusExtentions where T : DataBusDefinition - { - /// - /// Default constructor. - /// - public DataBusExtentions(SettingsHolder settings) - : base(settings) - { - } - } - - /// - /// This class provides implementers of databus with an extension mechanism for custom settings via extention methods. - /// - public class DataBusExtentions : ExposeSettings - { - /// - /// Default constructor. - /// - public DataBusExtentions(SettingsHolder settings) - : base(settings) - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBusFileBased.cs b/src/NServiceBus.Core/DataBus/DataBusFileBased.cs index da472c357fc..8dd8d83ecdc 100644 --- a/src/NServiceBus.Core/DataBus/DataBusFileBased.cs +++ b/src/NServiceBus.Core/DataBus/DataBusFileBased.cs @@ -11,26 +11,18 @@ public DataBusFileBased() } /// - /// See + /// See /// protected internal override void Setup(FeatureConfigurationContext context) { - // We still doing this check here eventhough we now have an explicit API - // to register custom IDataBus implementations for backwards compatibility - Type dataBusDefinitionType; - var customDataBusComponentRegistered = context.Container.HasComponent(); - - if (!context.Settings.TryGet("dataBusDefinitionType", out dataBusDefinitionType) && !customDataBusComponentRegistered) + string basePath; + if (!context.Settings.TryGet("FileShareDataBusPath", out basePath)) { - string basePath; - if (!context.Settings.TryGet("FileShareDataBusPath", out basePath)) - { - throw new InvalidOperationException("Messages containing databus properties found, please configure a databus using the ConfigureFileShareDataBus.FileShareDataBus extension method for ConfigurationBuilder."); - } - var dataBus = new FileShareDataBusImplementation(basePath); - - context.Container.RegisterSingleton(dataBus); + throw new InvalidOperationException("Specify the basepath for FileShareDataBus, eg endpointConfiguration.UseDataBus().BasePath(\"c:\\databus\")"); } + var dataBus = new FileShareDataBusImplementation(basePath); + + context.Container.RegisterSingleton(dataBus); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBusProperty.cs b/src/NServiceBus.Core/DataBus/DataBusProperty.cs new file mode 100644 index 00000000000..4381d0d5ee0 --- /dev/null +++ b/src/NServiceBus.Core/DataBus/DataBusProperty.cs @@ -0,0 +1,91 @@ +namespace NServiceBus +{ + using System; + using System.Runtime.Serialization; + using System.Security; + + /// + /// Default implementation for . + /// + /// Type of data to store in . + [Serializable] + public class DataBusProperty : IDataBusProperty, ISerializable where T : class + { + /// + /// initializes a with the . + /// + /// The value to initialize with. + public DataBusProperty(T value) + { + SetValue(value); + } + + /// + /// For serialization purposes. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + /// The caller does not have the required permission. + protected DataBusProperty(SerializationInfo info, StreamingContext context) + { + Guard.AgainstNull(nameof(info), info); + Key = info.GetString("Key"); + HasValue = info.GetBoolean("HasValue"); + } + + /// + /// The value. + /// + // ReSharper disable once ConvertToAutoPropertyWithPrivateSetter + public T Value => value; + + /// + /// The key. + /// + public string Key { get; set; } + + /// + /// true if has a value. + /// + public bool HasValue { get; set; } + + /// + /// Sets the value for . + /// + /// The value to set. + public void SetValue(object valueToSet) + { + value = valueToSet as T; + + if (value != null) + { + HasValue = true; + } + } + + /// + /// Gets the value of the . + /// + /// The value. + public object GetValue() + { + return Value; + } + + + /// + /// Populates a with the data needed to serialize the target object. + /// + /// The to populate with data. + /// The destination (see ) for this serialization. + /// The caller does not have the required permission. + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + Guard.AgainstNull(nameof(info), info); + info.AddValue("Key", Key); + info.AddValue("HasValue", HasValue); + } + + T value; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBusPropertyInfo.cs b/src/NServiceBus.Core/DataBus/DataBusPropertyInfo.cs similarity index 100% rename from src/NServiceBus.Core/DataBusPropertyInfo.cs rename to src/NServiceBus.Core/DataBus/DataBusPropertyInfo.cs index 3eda3a518fe..4e0f45d1dd9 100644 --- a/src/NServiceBus.Core/DataBusPropertyInfo.cs +++ b/src/NServiceBus.Core/DataBus/DataBusPropertyInfo.cs @@ -4,8 +4,8 @@ namespace NServiceBus class DataBusPropertyInfo { - public string Name; public Func Getter; + public string Name; public Action Setter; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DataBusReceiveBehavior.cs b/src/NServiceBus.Core/DataBus/DataBusReceiveBehavior.cs index a4c6198119a..5bb31d407bd 100644 --- a/src/NServiceBus.Core/DataBus/DataBusReceiveBehavior.cs +++ b/src/NServiceBus.Core/DataBus/DataBusReceiveBehavior.cs @@ -1,24 +1,25 @@ namespace NServiceBus { using System; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.DataBus; + using DataBus; using Pipeline; - using Pipeline.Contexts; - class DataBusReceiveBehavior : IBehavior + class DataBusReceiveBehavior : IBehavior { - public IDataBus DataBus { get; set; } - - public IDataBusSerializer DataBusSerializer { get; set; } - - public Conventions Conventions { get; set; } + public DataBusReceiveBehavior(IDataBus databus, IDataBusSerializer serializer, Conventions conventions) + { + this.conventions = conventions; + dataBusSerializer = serializer; + dataBus = databus; + } - public void Invoke(IncomingContext context, Action next) + public async Task Invoke(IIncomingLogicalMessageContext context, Func next) { - var message = context.IncomingLogicalMessage.Instance; + var message = context.Message.Instance; - foreach (var property in Conventions.GetDataBusProperties(message)) + foreach (var property in conventions.GetDataBusProperties(message)) { var propertyValue = property.Getter(message); @@ -31,41 +32,46 @@ public void Invoke(IncomingContext context, Action next) } else { - headerKey = String.Format("{0}.{1}", message.GetType().FullName, property.Name); + headerKey = $"{message.GetType().FullName}.{property.Name}"; } string dataBusKey; - if (!context.IncomingLogicalMessage.Headers.TryGetValue("NServiceBus.DataBus." + headerKey, out dataBusKey)) + if (!context.Headers.TryGetValue("NServiceBus.DataBus." + headerKey, out dataBusKey)) { continue; } - using (new TransactionScope(TransactionScopeOption.Suppress)) - using (var stream = DataBus.Get(dataBusKey)) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - var value = DataBusSerializer.Deserialize(stream); - - if (dataBusProperty != null) + using (var stream = await dataBus.Get(dataBusKey).ConfigureAwait(false)) { - dataBusProperty.SetValue(value); - } - else - { - property.Setter(message, value); + var value = dataBusSerializer.Deserialize(stream); + + if (dataBusProperty != null) + { + dataBusProperty.SetValue(value); + } + else + { + property.Setter(message, value); + } } } } - next(); + await next(context).ConfigureAwait(false); } + Conventions conventions; + IDataBus dataBus; + IDataBusSerializer dataBusSerializer; + public class Registration : RegisterStep { - public Registration() : base("DataBusReceive", typeof(DataBusReceiveBehavior), "Copies the databus shared data back to the logical message") + public Registration(Conventions conventions) : base("DataBusReceive", typeof(DataBusReceiveBehavior), "Copies the databus shared data back to the logical message", b => new DataBusReceiveBehavior(b.Build(), b.Build(), conventions)) { - InsertAfter(WellKnownStep.MutateIncomingMessages); - InsertBefore(WellKnownStep.InvokeHandlers); + InsertAfter("MutateIncomingMessages"); } } } diff --git a/src/NServiceBus.Core/DataBus/DataBusSendBehavior.cs b/src/NServiceBus.Core/DataBus/DataBusSendBehavior.cs index bbed58b4536..2b33cb5a597 100644 --- a/src/NServiceBus.Core/DataBus/DataBusSendBehavior.cs +++ b/src/NServiceBus.Core/DataBus/DataBusSendBehavior.cs @@ -2,37 +2,43 @@ { using System; using System.IO; + using System.Threading.Tasks; using System.Transactions; - using NServiceBus.DataBus; + using DataBus; + using DeliveryConstraints; + using Performance.TimeToBeReceived; using Pipeline; - using Pipeline.Contexts; - using Unicast.Transport; - class DataBusSendBehavior : IBehavior + class DataBusSendBehavior : IBehavior { - public IDataBus DataBus { get; set; } + public DataBusSendBehavior(IDataBus databus, IDataBusSerializer serializer, Conventions conventions) + { + this.conventions = conventions; + dataBusSerializer = serializer; + dataBus = databus; + } - public IDataBusSerializer DataBusSerializer { get; set; } + public async Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + var timeToBeReceived = TimeSpan.MaxValue; - public Conventions Conventions { get; set; } + DiscardIfNotReceivedBefore constraint; - public void Invoke(OutgoingContext context, Action next) - { - if (context.OutgoingLogicalMessage.IsControlMessage()) + if (context.Extensions.TryGetDeliveryConstraint(out constraint)) { - next(); - return; + timeToBeReceived = constraint.MaxTime; } - var timeToBeReceived = context.OutgoingLogicalMessage.Metadata.TimeToBeReceived; - var message = context.OutgoingLogicalMessage.Instance; + var message = context.Message.Instance; - foreach (var property in Conventions.GetDataBusProperties(message)) + foreach (var property in conventions.GetDataBusProperties(message)) { var propertyValue = property.Getter(message); if (propertyValue == null) + { continue; + } using (var stream = new MemoryStream()) { @@ -43,14 +49,14 @@ public void Invoke(OutgoingContext context, Action next) propertyValue = dataBusProperty.GetValue(); } - DataBusSerializer.Serialize(propertyValue, stream); + dataBusSerializer.Serialize(propertyValue, stream); stream.Position = 0; string headerValue; - using (new TransactionScope(TransactionScopeOption.Suppress)) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - headerValue = DataBus.Put(stream, timeToBeReceived); + headerValue = await dataBus.Put(stream, timeToBeReceived).ConfigureAwait(false); } string headerKey; @@ -64,24 +70,28 @@ public void Invoke(OutgoingContext context, Action next) else { property.Setter(message, null); - headerKey = String.Format("{0}.{1}", message.GetType().FullName, property.Name); + headerKey = $"{message.GetType().FullName}.{property.Name}"; } //we use the headers to in order to allow the infrastructure (eg. the gateway) to modify the actual key - context.OutgoingLogicalMessage.Headers["NServiceBus.DataBus." + headerKey] = headerValue; + context.Headers["NServiceBus.DataBus." + headerKey] = headerValue; } } - next(); + await next(context).ConfigureAwait(false); } + Conventions conventions; + IDataBus dataBus; + IDataBusSerializer dataBusSerializer; + public class Registration : RegisterStep { - public Registration(): base("DataBusSend", typeof(DataBusSendBehavior), "Saves the payload into the shared location") + public Registration(Conventions conventions) : base("DataBusSend", typeof(DataBusSendBehavior), "Saves the payload into the shared location", b => new DataBusSendBehavior(b.Build(), b.Build(), conventions)) { - InsertAfter(WellKnownStep.MutateOutgoingMessages); - InsertBefore(WellKnownStep.CreatePhysicalMessage); + InsertAfter("MutateOutgoingMessages"); + InsertAfter("ApplyTimeToBeReceived"); } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/DefaultDatabusSerializer.cs b/src/NServiceBus.Core/DataBus/DefaultDatabusSerializer.cs index 01cf87438d4..c8dd9bfe757 100644 --- a/src/NServiceBus.Core/DataBus/DefaultDatabusSerializer.cs +++ b/src/NServiceBus.Core/DataBus/DefaultDatabusSerializer.cs @@ -2,20 +2,20 @@ namespace NServiceBus { using System.IO; using System.Runtime.Serialization.Formatters.Binary; - using NServiceBus.DataBus; + using DataBus; class DefaultDataBusSerializer : IDataBusSerializer - { - static BinaryFormatter formatter = new BinaryFormatter(); - - public void Serialize(object databusProperty, Stream stream) - { - formatter.Serialize(stream, databusProperty); - } + { + public void Serialize(object databusProperty, Stream stream) + { + formatter.Serialize(stream, databusProperty); + } - public object Deserialize(Stream stream) - { - return formatter.Deserialize(stream); - } - } + public object Deserialize(Stream stream) + { + return formatter.Deserialize(stream); + } + + static BinaryFormatter formatter = new BinaryFormatter(); + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/FileShareDataBus.cs b/src/NServiceBus.Core/DataBus/FileShareDataBus.cs index a8cc48cc03a..272c16478d7 100644 --- a/src/NServiceBus.Core/DataBus/FileShareDataBus.cs +++ b/src/NServiceBus.Core/DataBus/FileShareDataBus.cs @@ -1,19 +1,20 @@ namespace NServiceBus { using System; - using NServiceBus.DataBus; + using DataBus; + using Features; /// - /// Base class for data bus definitions + /// Base class for data bus definitions. /// public class FileShareDataBus : DataBusDefinition { /// - /// The feature to enable when this databus is selected + /// The feature to enable when this databus is selected. /// protected internal override Type ProvidedByFeature() { - return typeof(Features.DataBusFileBased); + return typeof(DataBusFileBased); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/FileShareDataBusImplementation.cs b/src/NServiceBus.Core/DataBus/FileShareDataBusImplementation.cs index 3bd329580fb..5085f1c876a 100644 --- a/src/NServiceBus.Core/DataBus/FileShareDataBusImplementation.cs +++ b/src/NServiceBus.Core/DataBus/FileShareDataBusImplementation.cs @@ -1,94 +1,74 @@ -namespace NServiceBus.DataBus +namespace NServiceBus { using System; using System.IO; + using System.Threading.Tasks; + using DataBus; using Logging; - /// - /// File share implementation of . - /// class FileShareDataBusImplementation : IDataBus - { - string basePath; - static ILog logger = LogManager.GetLogger(); + { + public FileShareDataBusImplementation(string basePath) + { + this.basePath = basePath; + } + + public TimeSpan MaxMessageTimeToLive { get; set; } - /// - /// Create a with the specified . - /// - /// The path to save files on. - public FileShareDataBusImplementation(string basePath) - { - this.basePath = basePath; - } - - /// - /// Gets/Sets the maximum message TTL. - /// - public TimeSpan MaxMessageTimeToLive { get; set; } - - /// - /// Gets a data item from the bus. - /// - /// The key to look for. - /// The data . - public Stream Get(string key) - { + public Task Get(string key) + { var filePath = Path.Combine(basePath, key); logger.DebugFormat("Opening stream from '{0}'.", filePath); - return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - } - - /// - /// Adds a data item to the bus and returns the assigned key. - /// - /// A create containing the data to be sent on the databus. - /// The time to be received specified on the message type. TimeSpan.MaxValue is the default. - public string Put(Stream stream, TimeSpan timeToBeReceived) - { - var key = GenerateKey(timeToBeReceived); + var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true); + return Task.FromResult((Stream) fileStream); + } - var filePath = Path.Combine(basePath, key); + public async Task Put(Stream stream, TimeSpan timeToBeReceived) + { + var key = GenerateKey(timeToBeReceived); - Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + var filePath = Path.Combine(basePath, key); - using (var output = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read)) - { - var buffer = new byte[32 * 1024]; - int read; + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); - while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, read); - } - } + using (var output = new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, 4096, FileOptions.Asynchronous)) + { + const int bufferSize = 32*1024; + await stream.CopyToAsync(output, bufferSize).ConfigureAwait(false); + } logger.DebugFormat("Saved stream to '{0}'.", filePath); - return key; - } + return key; + } + + public Task Start() + { + logger.Info("File share data bus started. Location: " + basePath); + //TODO: Implement a clean up thread + return TaskEx.CompletedTask; + } - /// - /// Called when the bus starts up to allow the data bus to active background tasks. - /// - public void Start() - { - logger.Info("File share data bus started. Location: " + basePath); - //TODO: Implement a clean up thread - } + string GenerateKey(TimeSpan timeToBeReceived) + { + if (timeToBeReceived > MaxMessageTimeToLive) + { + timeToBeReceived = MaxMessageTimeToLive; + } - string GenerateKey(TimeSpan timeToBeReceived) - { - if (timeToBeReceived > MaxMessageTimeToLive) - timeToBeReceived = MaxMessageTimeToLive; + var keepMessageUntil = DateTime.MaxValue; - var keepMessageUntil = DateTime.MaxValue; + if (timeToBeReceived < TimeSpan.MaxValue) + { + keepMessageUntil = DateTime.Now + timeToBeReceived; + } - if (timeToBeReceived < TimeSpan.MaxValue) - keepMessageUntil = DateTime.Now + timeToBeReceived; + return Path.Combine(keepMessageUntil.ToString("yyyy-MM-dd_HH"), Guid.NewGuid().ToString()); + } - return Path.Combine(keepMessageUntil.ToString("yyyy-MM-dd_HH"), Guid.NewGuid().ToString()); - } - } + string basePath; + static ILog logger = LogManager.GetLogger(); + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/IDataBus.cs b/src/NServiceBus.Core/DataBus/IDataBus.cs index 90d5cf3fca6..0808a9c69a7 100644 --- a/src/NServiceBus.Core/DataBus/IDataBus.cs +++ b/src/NServiceBus.Core/DataBus/IDataBus.cs @@ -2,6 +2,7 @@ namespace NServiceBus.DataBus { using System; using System.IO; + using System.Threading.Tasks; /// /// The main interface for interactions with the databus. @@ -12,19 +13,19 @@ public interface IDataBus /// Gets a data item from the bus. /// /// The key to look for. - /// The data . - Stream Get(string key); + /// The data . + Task Get(string key); /// /// Adds a data item to the bus and returns the assigned key. /// /// A create containing the data to be sent on the databus. /// The time to be received specified on the message type. TimeSpan.MaxValue is the default. - string Put(Stream stream, TimeSpan timeToBeReceived); + Task Put(Stream stream, TimeSpan timeToBeReceived); /// /// Called when the bus starts up to allow the data bus to active background tasks. /// - void Start(); + Task Start(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/IDataBusProperty.cs b/src/NServiceBus.Core/DataBus/IDataBusProperty.cs new file mode 100644 index 00000000000..d091cd8067f --- /dev/null +++ b/src/NServiceBus.Core/DataBus/IDataBusProperty.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + /// + /// The contract to implement a . + /// + public interface IDataBusProperty + { + /// + /// The key. + /// + string Key { get; set; } + + /// + /// true if has a value. + /// + bool HasValue { get; set; } + + /// + /// Gets the value of the . + /// + object GetValue(); + + /// + /// Sets the value for . + /// + void SetValue(object value); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/IDatabusSerializer.cs b/src/NServiceBus.Core/DataBus/IDatabusSerializer.cs index 4edf2e7e788..8d87bc4e2c7 100644 --- a/src/NServiceBus.Core/DataBus/IDatabusSerializer.cs +++ b/src/NServiceBus.Core/DataBus/IDatabusSerializer.cs @@ -3,22 +3,22 @@ namespace NServiceBus.DataBus using System.IO; /// - /// Interface used for serializing and deserializing of databus properties. - /// - public interface IDataBusSerializer - { - /// - /// Serializes the property into the given stream. - /// - /// The property to serialize. - /// The stream to which to write the property.> - void Serialize(object databusProperty, Stream stream); + /// Interface used for serializing and deserializing of databus properties. + /// + public interface IDataBusSerializer + { + /// + /// Serializes the property into the given stream. + /// + /// The property to serialize. + /// The stream to which to write the property. + void Serialize(object databusProperty, Stream stream); - /// - /// Deserializes a property from the given stream. - /// + /// + /// Deserializes a property from the given stream. + /// /// The stream from which to read the property. - /// The deserialized object. - object Deserialize(Stream stream); - } + /// The deserialized object. + object Deserialize(Stream stream); + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBus/UseDataBusExtensions.cs b/src/NServiceBus.Core/DataBus/UseDataBusExtensions.cs index 4069c17699b..e236ec62793 100644 --- a/src/NServiceBus.Core/DataBus/UseDataBusExtensions.cs +++ b/src/NServiceBus.Core/DataBus/UseDataBusExtensions.cs @@ -1,46 +1,52 @@ namespace NServiceBus { using System; - using NServiceBus.DataBus; + using DataBus; /// - /// Extension methods to configure data bus + /// Extension methods to configure data bus. /// public static class UseDataBusExtensions { /// /// Configures NServiceBus to use the given data bus definition. /// - public static DataBusExtentions UseDataBus(this BusConfiguration config) where T : DataBusDefinition, new() + /// The instance to apply the settings to. + public static DataBusExtensions UseDataBus(this EndpointConfiguration config) where T : DataBusDefinition, new() { - var type = typeof(DataBusExtentions<>).MakeGenericType(typeof(T)); - var extension = (DataBusExtentions)Activator.CreateInstance(type, config.Settings); - var definition = (DataBusDefinition)Activator.CreateInstance(typeof(T)); + Guard.AgainstNull(nameof(config), config); + var type = typeof(DataBusExtensions<>).MakeGenericType(typeof(T)); + var extension = (DataBusExtensions) Activator.CreateInstance(type, config.Settings); + var definition = (DataBusDefinition) Activator.CreateInstance(typeof(T)); config.Settings.Set("SelectedDataBus", definition); + config.EnableFeature(); + return extension; } /// - /// Configures NServiceBus to use a custom implementation. + /// Configures NServiceBus to use a custom implementation. /// - public static DataBusExtentions UseDataBus(this BusConfiguration config, Type dataBusType) + /// The instance to apply the settings to. + /// The to use. + public static DataBusExtensions UseDataBus(this EndpointConfiguration config, Type dataBusType) { - if (dataBusType == null) - { - throw new ArgumentNullException("dataBusType"); - } + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(dataBusType), dataBusType); if (!typeof(IDataBus).IsAssignableFrom(dataBusType)) { - throw new ArgumentException("The type needs to implement IDataBus.", "dataBusType"); + throw new ArgumentException("The type needs to implement IDataBus.", nameof(dataBusType)); } config.Settings.Set("SelectedDataBus", new CustomDataBus()); config.Settings.Set("CustomDataBusType", dataBusType); - return new DataBusExtentions(config.Settings); + config.EnableFeature(); + + return new DataBusExtensions(config.Settings); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DataBusProperty.cs b/src/NServiceBus.Core/DataBusProperty.cs deleted file mode 100644 index 0fb8cb24575..00000000000 --- a/src/NServiceBus.Core/DataBusProperty.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Runtime.Serialization; - using System.Security; - - /// - /// Default implementation for . - /// - /// Type of data to store in . - [Serializable] - public class DataBusProperty : IDataBusProperty, ISerializable where T : class - { - T value; - - /// - /// initializes a with the . - /// - /// The value to initialise with. - public DataBusProperty(T value) - { - SetValue(value); - } - - /// - /// For serialization purposes. - /// - /// The to populate with data. The destination (see ) for this serialization. The caller does not have the required permission. - protected DataBusProperty(SerializationInfo info, StreamingContext context) - { - Key = info.GetString("Key"); - HasValue = info.GetBoolean("HasValue"); - } - - /// - /// The key. - /// - public string Key { get; set; } - - /// - /// true if has a value. - /// - public bool HasValue { get; set; } - - /// - /// The value. - /// - public T Value - { - get - { - return value; - } - } - - - /// - /// Populates a with the data needed to serialize the target object. - /// - /// The to populate with data. The destination (see ) for this serialization. The caller does not have the required permission. - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("Key", Key); - info.AddValue("HasValue", HasValue); - } - - /// - /// Sets the value for . - /// - /// The value to set. - public void SetValue(object valueToSet) - { - value = valueToSet as T; - - if (value != null) - HasValue = true; - } - - /// - /// Gets the value of the . - /// - /// The value - public object GetValue() - { - return Value; - } - - } - - /// - /// The contract to implement a . - /// - public interface IDataBusProperty - { - /// - /// The key. - /// - string Key { get; set; } - - /// - /// Gets the value of the . - /// - /// The value - object GetValue(); - - /// - /// Sets the value for . - /// - /// The value to set. - void SetValue(object value); - - /// - /// true if has a value. - /// - bool HasValue { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/DateTimeExtensions.cs b/src/NServiceBus.Core/DateTimeExtensions.cs index 942506cd4ae..b3c0e65560c 100644 --- a/src/NServiceBus.Core/DateTimeExtensions.cs +++ b/src/NServiceBus.Core/DateTimeExtensions.cs @@ -4,26 +4,97 @@ namespace NServiceBus using System.Globalization; /// - /// Common date time extensions + /// Common date time extensions. /// public static class DateTimeExtensions { - const string Format = "yyyy-MM-dd HH:mm:ss:ffffff Z"; - /// - /// Converts the to a suitable for transport over the wire + /// Converts the to a suitable for transport over the wire. /// public static string ToWireFormattedString(DateTime dateTime) { - return dateTime.ToUniversalTime().ToString(Format, CultureInfo.InvariantCulture); + return dateTime.ToUniversalTime().ToString(format, CultureInfo.InvariantCulture); } /// - /// Converts a wire formatted from to a UTC + /// Converts a wire formatted from to a UTC + /// . /// public static DateTime ToUtcDateTime(string wireFormattedString) { - return DateTime.ParseExact(wireFormattedString, Format, CultureInfo.InvariantCulture).ToUniversalTime(); + Guard.AgainstNullAndEmpty(nameof(wireFormattedString), wireFormattedString); + + if (wireFormattedString.Length != format.Length) + { + throw new FormatException(errorMessage); + } + + var year = 0; + var month = 0; + var day = 0; + var hour = 0; + var minute = 0; + var second = 0; + var microSecond = 0; + + for (var i = 0; i < format.Length; i++) + { + var digit = wireFormattedString[i]; + + switch (format[i]) + { + case 'y': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + year = year * 10 + (digit - '0'); + break; + + case 'M': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + month = month * 10 + (digit - '0'); + break; + + case 'd': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + day = day * 10 + (digit - '0'); + break; + + case 'H': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + hour = hour * 10 + (digit - '0'); + break; + + case 'm': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + minute = minute * 10 + (digit - '0'); + break; + + case 's': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + second = second * 10 + (digit - '0'); + break; + + case 'f': + if (digit < '0' || digit > '9') throw new FormatException(errorMessage); + microSecond = microSecond * 10 + (digit - '0'); + break; + } + } + + return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc).AddMicroseconds(microSecond); + } + + internal static int Microseconds(this DateTime self) + { + return (int)Math.Floor(self.Ticks % TimeSpan.TicksPerMillisecond / (double)ticksPerMicrosecond); } + + internal static DateTime AddMicroseconds(this DateTime self, int microseconds) + { + return self.AddTicks(microseconds * ticksPerMicrosecond); + } + + const string format = "yyyy-MM-dd HH:mm:ss:ffffff Z"; + const string errorMessage = "String was not recognized as a valid DateTime."; + const int ticksPerMicrosecond = 10; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/DelayDeliveryWith.cs b/src/NServiceBus.Core/DelayedDelivery/DelayDeliveryWith.cs new file mode 100644 index 00000000000..204d7b8b1b8 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/DelayDeliveryWith.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.DelayedDelivery +{ + using System; + + /// + /// Represent a constraint that the message can't be delivered before the specified delay has elapsed. + /// + public class DelayDeliveryWith : DelayedDeliveryConstraint + { + /// + /// Initializes a new instance of . + /// + /// How long to delay the delivery of the message. + public DelayDeliveryWith(TimeSpan delay) + { + Guard.AgainstNegative(nameof(delay), delay); + + Delay = delay; + } + + /// + /// The requested delay. + /// + public TimeSpan Delay { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryConstraint.cs b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryConstraint.cs new file mode 100644 index 00000000000..9bf68cfa4de --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryConstraint.cs @@ -0,0 +1,11 @@ +namespace NServiceBus.DelayedDelivery +{ + using DeliveryConstraints; + + /// + /// Base for the 2 flavors of delayed delivery. + /// + public abstract class DelayedDeliveryConstraint : DeliveryConstraint + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryFeature.cs b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryFeature.cs new file mode 100644 index 00000000000..d35801bf730 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryFeature.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.Features +{ + using Config; + using DelayedDelivery; + using DeliveryConstraints; + + class DelayedDeliveryFeature : Feature + { + public DelayedDeliveryFeature() + { + EnableByDefault(); + DependsOnOptionally(); + Defaults(s => + { + var timeoutManagerAddressConfiguration = new TimeoutManagerAddressConfiguration(s.GetConfigSection()?.TimeoutManagerAddress); + s.Set(timeoutManagerAddressConfiguration); + }); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var transportHasNativeDelayedDelivery = context.Settings.DoesTransportSupportConstraint(); + var timeoutManagerAddress = context.Settings.Get().TransportAddress; + + if (!transportHasNativeDelayedDelivery) + { + if (timeoutManagerAddress == null) + { + DoNotClearTimeouts(context); + context.Pipeline.Register("ThrowIfCannotDeferMessage", new ThrowIfCannotDeferMessageBehavior(), "Throws an exception if an attempt is made to defer a message without infrastructure support."); + } + else + { + context.Pipeline.Register("RouteDeferredMessageToTimeoutManager", new RouteDeferredMessageToTimeoutManagerBehavior(timeoutManagerAddress), "Reroutes deferred messages to the timeout manager"); + context.Container.ConfigureComponent(b => new RequestCancelingOfDeferredMessagesFromTimeoutManager(timeoutManagerAddress), DependencyLifecycle.SingleInstance); + } + } + else + { + DoNotClearTimeouts(context); + } + } + + static void DoNotClearTimeouts(FeatureConfigurationContext context) + { + context.Container.ConfigureComponent(b => new NoOpCanceling(), DependencyLifecycle.SingleInstance); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryOptionExtensions.cs b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryOptionExtensions.cs new file mode 100644 index 00000000000..d1a4e50067b --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/DelayedDeliveryOptionExtensions.cs @@ -0,0 +1,64 @@ +namespace NServiceBus +{ + using System; + using DelayedDelivery; + using DeliveryConstraints; + using Extensibility; + + /// + /// Provides ways for the end user to request delayed delivery of their messages. + /// + public static class DelayedDeliveryOptionExtensions + { + /// + /// Delays the delivery of the message with the specified delay. + /// + /// The options being extended. + /// The requested delay. + public static void DelayDeliveryWith(this SendOptions options, TimeSpan delay) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNegative(nameof(delay), delay); + + options.GetExtensions().AddDeliveryConstraint(new DelayDeliveryWith(delay)); + } + + /// + /// Returns the configured delivery delay by using . + /// + /// The options being extended. + /// The configured or null. + public static TimeSpan? GetDeliveryDelay(this SendOptions options) + { + DelayDeliveryWith delay; + options.GetExtensions().TryGetDeliveryConstraint(out delay); + + return delay?.Delay; + } + + /// + /// Requests that the message should not be delivered before the specified time. + /// + /// The options being extended. + /// The time when this message should be made available. + public static void DoNotDeliverBefore(this SendOptions options, DateTimeOffset at) + { + Guard.AgainstNull(nameof(options), options); + + options.GetExtensions().AddDeliveryConstraint(new DoNotDeliverBefore(at.UtcDateTime)); + } + + /// + /// Returns the delivery date configured by using . + /// + /// The options being extended. + /// The configured or null. + public static DateTimeOffset? GetDeliveryDate(this SendOptions options) + { + DoNotDeliverBefore deliveryDate; + options.GetExtensions().TryGetDeliveryConstraint(out deliveryDate); + + return deliveryDate?.At; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/DoNotDeliverBefore.cs b/src/NServiceBus.Core/DelayedDelivery/DoNotDeliverBefore.cs new file mode 100644 index 00000000000..04964cc92aa --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/DoNotDeliverBefore.cs @@ -0,0 +1,24 @@ +namespace NServiceBus.DelayedDelivery +{ + using System; + + /// + /// Represent a constraint that the message can't be made available for consumption before a given time. + /// + public class DoNotDeliverBefore : DelayedDeliveryConstraint + { + /// + /// Initializes a new instance of . + /// + /// The earliest time this message should be made available to its consumers. + public DoNotDeliverBefore(DateTime at) + { + At = at; + } + + /// + /// The actual time when the message can be available to the recipient. + /// + public DateTime At { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/NoOpCanceling.cs b/src/NServiceBus.Core/DelayedDelivery/NoOpCanceling.cs new file mode 100644 index 00000000000..70531f61496 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/NoOpCanceling.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class NoOpCanceling : ICancelDeferredMessages + { + public Task CancelDeferredMessages(string messageKey, IBehaviorContext context) + { + //no-op + return TaskEx.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/RequestCancelingOfDeferredMessagesFromTimeoutManager.cs b/src/NServiceBus.Core/DelayedDelivery/RequestCancelingOfDeferredMessagesFromTimeoutManager.cs new file mode 100644 index 00000000000..6938d5a8e9b --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/RequestCancelingOfDeferredMessagesFromTimeoutManager.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Transport; + using Unicast.Transport; + + class RequestCancelingOfDeferredMessagesFromTimeoutManager : ICancelDeferredMessages + { + public RequestCancelingOfDeferredMessagesFromTimeoutManager(string timeoutManagerAddress) + { + this.timeoutManagerAddress = timeoutManagerAddress; + } + + public Task CancelDeferredMessages(string messageKey, IBehaviorContext context) + { + var controlMessage = ControlMessageFactory.Create(MessageIntentEnum.Send); + + controlMessage.Headers[Headers.SagaId] = messageKey; + controlMessage.Headers[TimeoutManagerHeaders.ClearTimeouts] = bool.TrueString; + + var dispatchContext = new RoutingContext(controlMessage, new UnicastRoutingStrategy(timeoutManagerAddress), context); + + var cache = context.Extensions.Get(); + var dispatchPipeline = cache.Pipeline(); + return dispatchPipeline.Invoke(dispatchContext); + } + + string timeoutManagerAddress; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehavior.cs b/src/NServiceBus.Core/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehavior.cs new file mode 100644 index 00000000000..c77be92db78 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/RouteDeferredMessageToTimeoutManagerBehavior.cs @@ -0,0 +1,73 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using Extensibility; + using Performance.TimeToBeReceived; + using Pipeline; + using Routing; + + class RouteDeferredMessageToTimeoutManagerBehavior : IBehavior + { + public RouteDeferredMessageToTimeoutManagerBehavior(string timeoutManagerAddress) + { + this.timeoutManagerAddress = timeoutManagerAddress; + } + + public Task Invoke(IRoutingContext context, Func next) + { + DateTime deliverAt; + if (!IsDeferred(context, out deliverAt)) + { + return next(context); + } + + DiscardIfNotReceivedBefore discardIfNotReceivedBefore; + if (context.Extensions.TryGetDeliveryConstraint(out discardIfNotReceivedBefore)) + { + throw new Exception("Postponed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to postpone messages of this type."); + } + + var newRoutingStrategies = context.RoutingStrategies.Select(s => RerouteToTimeoutManager(s, context, deliverAt)); + context.RoutingStrategies = newRoutingStrategies.ToArray(); + + return next(context); + } + + RoutingStrategy RerouteToTimeoutManager(RoutingStrategy routingStrategy, IRoutingContext context, DateTime deliverAt) + { + var headers = new Dictionary(context.Message.Headers); + var originalTag = routingStrategy.Apply(headers); + var unicastTag = originalTag as UnicastAddressTag; + if (unicastTag == null) + { + throw new Exception("Delayed delivery using the Timeout Manager is only supported for messages with unicast routing"); + } + return new TimeoutManagerRoutingStrategy(timeoutManagerAddress, unicastTag.Destination, deliverAt); + } + + static bool IsDeferred(IExtendable context, out DateTime deliverAt) + { + deliverAt = DateTime.MinValue; + DoNotDeliverBefore doNotDeliverBefore; + DelayDeliveryWith delayDeliveryWith; + if (context.Extensions.TryRemoveDeliveryConstraint(out doNotDeliverBefore)) + { + deliverAt = doNotDeliverBefore.At; + return true; + } + if (context.Extensions.TryRemoveDeliveryConstraint(out delayDeliveryWith)) + { + deliverAt = DateTime.UtcNow + delayDeliveryWith.Delay; + return true; + } + return false; + } + + string timeoutManagerAddress; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/ThrowIfCannotDeferMessageBehavior.cs b/src/NServiceBus.Core/DelayedDelivery/ThrowIfCannotDeferMessageBehavior.cs new file mode 100644 index 00000000000..bf839379821 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/ThrowIfCannotDeferMessageBehavior.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using Pipeline; + + class ThrowIfCannotDeferMessageBehavior : IBehavior + { + public Task Invoke(IRoutingContext context, Func next) + { + var deliveryConstraints = context.Extensions.GetDeliveryConstraints(); + if (deliveryConstraints.Any(constraint => constraint is DelayedDeliveryConstraint)) + { + throw new InvalidOperationException("Cannot delay delivery of messages when TimeoutManager is disabled or there is no infrastructure support for delayed messages."); + } + + return next(context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ConfigurationTimeoutExtensions.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ConfigurationTimeoutExtensions.cs new file mode 100644 index 00000000000..54e2e6f41f5 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ConfigurationTimeoutExtensions.cs @@ -0,0 +1,24 @@ +namespace NServiceBus +{ + using System; + + /// + /// Extension methods declarations. + /// + public static class ConfigurationTimeoutExtensions + { + /// + /// A critical error is raised when timeout retrieval fails. + /// By default we wait for 2 seconds for the storage to come back. + /// This method allows to change the default and extend the wait time. + /// + /// The instance to apply the settings to. + /// Time to wait before raising a critical error. + public static void TimeToWaitBeforeTriggeringCriticalErrorOnTimeoutOutages(this EndpointConfiguration config, TimeSpan timeToWait) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNegative(nameof(timeToWait), timeToWait); + config.Settings.Set("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver", timeToWait); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/DispatchTimeoutBehavior.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/DispatchTimeoutBehavior.cs new file mode 100644 index 00000000000..7eaec19585f --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/DispatchTimeoutBehavior.cs @@ -0,0 +1,57 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Routing; + using Timeout.Core; + using Transport; + + class DispatchTimeoutBehavior + { + public DispatchTimeoutBehavior(IDispatchMessages dispatcher, IPersistTimeouts persister, TransportTransactionMode transportTransactionMode) + { + this.dispatcher = dispatcher; + this.persister = persister; + dispatchConsistency = GetDispatchConsistency(transportTransactionMode); + } + + public async Task Invoke(MessageContext context) + { + var timeoutId = context.Headers["Timeout.Id"]; + + var timeoutData = await persister.Peek(timeoutId, context.Context).ConfigureAwait(false); + + if (timeoutData == null) + { + return; + } + + timeoutData.Headers[Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); + timeoutData.Headers["NServiceBus.RelatedToTimeoutId"] = timeoutData.Id; + + var outgoingMessage = new OutgoingMessage(context.MessageId, timeoutData.Headers, timeoutData.State); + var transportOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(timeoutData.Destination), dispatchConsistency); + await dispatcher.Dispatch(new TransportOperations(transportOperation), context.TransportTransaction, context.Context).ConfigureAwait(false); + + var timeoutRemoved = await persister.TryRemove(timeoutId, context.Context).ConfigureAwait(false); + if (!timeoutRemoved) + { + // timeout was concurrently removed between Peek and TryRemove. Throw an exception to rollback the dispatched message if possible. + throw new Exception($"timeout '{timeoutId}' was concurrently processed."); + } + } + + static DispatchConsistency GetDispatchConsistency(TransportTransactionMode transportTransactionMode) + { + // dispatch message isolated from existing transactions when not using DTC to avoid loosing timeout data when the transaction commit fails. + return transportTransactionMode == TransportTransactionMode.TransactionScope + ? DispatchConsistency.Default + : DispatchConsistency.Isolated; + } + + readonly DispatchConsistency dispatchConsistency; + + IDispatchMessages dispatcher; + IPersistTimeouts persister; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPoller.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPoller.cs new file mode 100644 index 00000000000..7dd100de04c --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/ExpiredTimeoutsPoller.cs @@ -0,0 +1,152 @@ +namespace NServiceBus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Extensibility; + using Logging; + using Routing; + using Timeout.Core; + using Transport; + using Unicast.Transport; + + class ExpiredTimeoutsPoller : IDisposable + { + public ExpiredTimeoutsPoller(IQueryTimeouts timeoutsFetcher, IDispatchMessages dispatcher, string dispatcherAddress, ICircuitBreaker circuitBreaker, Func currentTimeProvider) + { + this.timeoutsFetcher = timeoutsFetcher; + this.dispatcher = dispatcher; + this.dispatcherAddress = dispatcherAddress; + this.circuitBreaker = circuitBreaker; + this.currentTimeProvider = currentTimeProvider; + + var now = currentTimeProvider(); + startSlice = now.AddYears(-10); + NextRetrieval = now; + } + + public DateTime NextRetrieval { get; private set; } + + public void Dispose() + { + //Injected + } + + public void Start() + { + tokenSource = new CancellationTokenSource(); + + var token = tokenSource.Token; + + timeoutPollerTask = Task.Run(() => Poll(token)); + } + + public Task Stop() + { + tokenSource.Cancel(); + return timeoutPollerTask; + } + + public void NewTimeoutRegistered(DateTime expiryTime) + { + lock (lockObject) + { + if (NextRetrieval > expiryTime) + { + NextRetrieval = expiryTime; + } + } + } + + async Task Poll(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + await InnerPoll(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // ok, since the InnerPoll could observe the token + } + catch (Exception ex) + { + Logger.Warn("Failed to fetch timeouts from the timeout storage", ex); + await circuitBreaker.Failure(ex).ConfigureAwait(false); + } + } + } + + async Task InnerPoll(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await SpinOnce(cancellationToken).ConfigureAwait(false); + await Task.Delay(NextRetrievalPollSleep, cancellationToken).ConfigureAwait(false); + } + } + + internal async Task SpinOnce(CancellationToken cancellationToken) + { + if (NextRetrieval > currentTimeProvider() || cancellationToken.IsCancellationRequested) + { + return; + } + + Logger.DebugFormat("Polling for timeouts at {0}.", currentTimeProvider()); + var timeoutChunk = await timeoutsFetcher.GetNextChunk(startSlice).ConfigureAwait(false); + + foreach (var timeoutData in timeoutChunk.DueTimeouts) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + if (startSlice < timeoutData.DueTime) + { + startSlice = timeoutData.DueTime; + } + + var dispatchRequest = ControlMessageFactory.Create(MessageIntentEnum.Send); + + dispatchRequest.Headers["Timeout.Id"] = timeoutData.Id; + + var transportOperation = new TransportOperation(dispatchRequest, new UnicastAddressTag(dispatcherAddress)); + await dispatcher.Dispatch(new TransportOperations(transportOperation), new TransportTransaction(), new ContextBag()).ConfigureAwait(false); + } + + lock (lockObject) + { + var nextTimeToQuery = timeoutChunk.NextTimeToQuery; + + // we cap the next retrieval to max 1 minute this will make sure that we trip the circuit breaker if we + // loose connectivity to our storage. This will also make sure that timeouts added (during migration) direct to storage + // will be picked up after at most 1 minute + var maxNextRetrieval = currentTimeProvider() + MaxNextRetrievalDelay; + + NextRetrieval = nextTimeToQuery > maxNextRetrieval ? maxNextRetrieval : nextTimeToQuery; + + Logger.DebugFormat("Polling next retrieval is at {0}.", NextRetrieval.ToLocalTime()); + } + + circuitBreaker.Success(); + } + + ICircuitBreaker circuitBreaker; + Func currentTimeProvider; + IDispatchMessages dispatcher; + string dispatcherAddress; + object lockObject = new object(); + DateTime startSlice; + Task timeoutPollerTask; + + IQueryTimeouts timeoutsFetcher; + CancellationTokenSource tokenSource; + + static ILog Logger = LogManager.GetLogger(); + static readonly TimeSpan MaxNextRetrievalDelay = TimeSpan.FromMinutes(1); + static readonly TimeSpan NextRetrievalPollSleep = TimeSpan.FromMilliseconds(1000); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IPersistTimeouts.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IPersistTimeouts.cs new file mode 100644 index 00000000000..c3c1eb22609 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IPersistTimeouts.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.Timeout.Core +{ + using System; + using System.Threading.Tasks; + using Extensibility; + + /// + /// Timeout persister contract. + /// + public interface IPersistTimeouts + { + /// + /// Adds a new timeout. + /// + /// Timeout data. + /// The current pipeline context. + Task Add(TimeoutData timeout, ContextBag context); + + /// + /// Removes the timeout if it hasn't been previously removed. + /// + /// The timeout id to remove. + /// The current pipeline context. + /// true when the timeout has successfully been removed by this method call, false otherwise. + Task TryRemove(string timeoutId, ContextBag context); + + /// + /// Returns the timeout with the given id from the storage. The timeout will remain in the storage. + /// + /// The id of the timeout to fetch. + /// The current pipeline context. + /// with the given id if present in the storage or null otherwise. + Task Peek(string timeoutId, ContextBag context); + + /// + /// Removes the timeouts by saga id. + /// + /// The saga id of the timeouts to remove. + /// The current pipeline context. + Task RemoveTimeoutBy(Guid sagaId, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IQueryTimeouts.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IQueryTimeouts.cs new file mode 100644 index 00000000000..f39366e15f5 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/IQueryTimeouts.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Timeout.Core +{ + using System; + using System.Threading.Tasks; + + /// + /// Allows to query for timeouts. + /// + public interface IQueryTimeouts + { + /// + /// Retrieves the next range of timeouts that are due. + /// + /// The time where to start retrieving the next slice, the slice should exclude this date. + /// Returns the next range of timeouts that are due. + Task GetNextChunk(DateTime startSlice); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutData.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutData.cs new file mode 100644 index 00000000000..29629cebbc1 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutData.cs @@ -0,0 +1,57 @@ +namespace NServiceBus.Timeout.Core +{ + using System; + using System.Collections.Generic; + + /// + /// Holds timeout information. + /// + public partial class TimeoutData + { + /// + /// Id of this timeout. + /// + public string Id { get; set; } + + /// + /// The address of the client who requested the timeout. + /// + public string Destination { get; set; } + + /// + /// The saga ID. + /// + public Guid SagaId { get; set; } + + /// + /// Additional state. + /// + public byte[] State { get; set; } + + /// + /// The time at which the timeout expires. + /// + public DateTime Time { get; set; } + + /// + /// The timeout manager that owns this particular timeout. + /// + public string OwningTimeoutManager { get; set; } + + /// + /// Store the headers to preserve them across timeouts. + /// + public Dictionary Headers { get; set; } + + /// + /// Returns a that represents the current . + /// + /// + /// A that represents the current . + /// + public override string ToString() + { + return $"Timeout({Id}) - Expires:{Time}, SagaId:{SagaId}"; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutsChunk.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutsChunk.cs new file mode 100644 index 00000000000..4786347dccb --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/Persistence/TimeoutsChunk.cs @@ -0,0 +1,58 @@ +namespace NServiceBus.Timeout.Core +{ + using System; + + /// + /// Contains a collection of timeouts that are due and when to query for timeouts again. + /// + public class TimeoutsChunk + { + /// + /// Creates a new instance of the timeouts chunk. + /// + /// timeouts that are due. + /// the next time to query for due timeouts again. + public TimeoutsChunk(Timeout[] dueTimeouts, DateTime nextTimeToQuery) + { + DueTimeouts = dueTimeouts; + NextTimeToQuery = nextTimeToQuery; + } + + /// + /// timeouts that are due. + /// + public Timeout[] DueTimeouts { get; private set; } + + /// + /// the next time to query for due timeouts again. + /// + public DateTime NextTimeToQuery { get; private set; } + + /// + /// Represents a timeout. + /// + public struct Timeout + { + /// + /// Creates a new instance of a timeout representation. + /// + /// The id of the timeout. + /// The due time of the timeout. + public Timeout(string id, DateTime dueTime) + { + Id = id; + DueTime = dueTime; + } + + /// + /// The id of the timeout. + /// + public string Id { get; } + + /// + /// The due time of the timeout. + /// + public DateTime DueTime { get; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/StoreTimeoutBehavior.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/StoreTimeoutBehavior.cs new file mode 100644 index 00000000000..8feef20ae52 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/StoreTimeoutBehavior.cs @@ -0,0 +1,90 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Routing; + using Timeout.Core; + using Transport; + + class StoreTimeoutBehavior + { + public StoreTimeoutBehavior(ExpiredTimeoutsPoller poller, IDispatchMessages dispatcher, IPersistTimeouts persister, string owningTimeoutManager) + { + this.poller = poller; + this.dispatcher = dispatcher; + this.persister = persister; + this.owningTimeoutManager = owningTimeoutManager; + } + + public async Task Invoke(MessageContext context) + { + var sagaId = Guid.Empty; + + string sagaIdString; + if (context.Headers.TryGetValue(Headers.SagaId, out sagaIdString)) + { + sagaId = Guid.Parse(sagaIdString); + } + + if (context.Headers.ContainsKey(TimeoutManagerHeaders.ClearTimeouts)) + { + if (sagaId == Guid.Empty) + { + throw new InvalidOperationException("Invalid saga id specified, clear timeouts is only supported for saga instances"); + } + + await persister.RemoveTimeoutBy(sagaId, context.Context).ConfigureAwait(false); + } + else + { + string expire; + if (!context.Headers.TryGetValue(TimeoutManagerHeaders.Expire, out expire)) + { + throw new InvalidOperationException("Non timeout message arrived at the timeout manager, id:" + context.MessageId); + } + + var destination = GetReplyToAddress(context); + + string routeExpiredTimeoutTo; + if (context.Headers.TryGetValue(TimeoutManagerHeaders.RouteExpiredTimeoutTo, out routeExpiredTimeoutTo)) + { + destination = routeExpiredTimeoutTo; + } + + var data = new TimeoutData + { + Destination = destination, + SagaId = sagaId, + State = context.Body, + Time = DateTimeExtensions.ToUtcDateTime(expire), + Headers = context.Headers, + OwningTimeoutManager = owningTimeoutManager + }; + + if (data.Time.AddSeconds(-1) <= DateTime.UtcNow) + { + var outgoingMessage = new OutgoingMessage(context.MessageId, data.Headers, data.State); + var transportOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(data.Destination)); + await dispatcher.Dispatch(new TransportOperations(transportOperation), context.TransportTransaction, context.Context).ConfigureAwait(false); + return; + } + + await persister.Add(data, context.Context).ConfigureAwait(false); + + poller.NewTimeoutRegistered(data.Time); + } + } + + static string GetReplyToAddress(MessageContext context) + { + string replyToAddress; + return context.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress) ? replyToAddress : null; + } + + IDispatchMessages dispatcher; + string owningTimeoutManager; + IPersistTimeouts persister; + + ExpiredTimeoutsPoller poller; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManager.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManager.cs new file mode 100644 index 00000000000..ade8437518c --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManager.cs @@ -0,0 +1,121 @@ +namespace NServiceBus.Features +{ + using System; + using ConsistencyGuarantees; + using DelayedDelivery; + using DeliveryConstraints; + using Persistence; + using Settings; + using Timeout.Core; + using Transport; + + /// + /// Used to configure the timeout manager that provides message deferral. + /// + public class TimeoutManager : Feature + { + internal TimeoutManager() + { + Defaults(s => s.SetDefault("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver", TimeToWaitBeforeTriggeringCriticalError)); + EnableByDefault(); + + Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"), "Send only endpoints can't use the timeoutmanager since it requires receive capabilities"); + Prerequisite(context => !HasAlternateTimeoutManagerBeenConfigured(context.Settings), "A user configured timeoutmanager address has been found and this endpoint will send timeouts to that endpoint"); + Prerequisite(c => !c.Settings.DoesTransportSupportConstraint(), "The selected transport supports delayed delivery natively"); + } + + /// + /// See . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + if (!PersistenceStartup.HasSupportFor(context.Settings)) + { + throw new Exception("The selected persistence doesn't have support for timeout storage. Select another persistence or disable the timeout manager feature using endpointConfiguration.DisableFeature()"); + } + + var requiredTransactionSupport = context.Settings.GetRequiredTransactionModeForReceives(); + + SetupStorageSatellite(context, requiredTransactionSupport); + + var dispatcherAddress = SetupDispatcherSatellite(context, requiredTransactionSupport); + + SetupTimeoutPoller(context, dispatcherAddress); + } + + static void SetupTimeoutPoller(FeatureConfigurationContext context, string dispatcherAddress) + { + context.Container.ConfigureComponent(b => + { + var waitTime = context.Settings.Get("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver"); + + var criticalError = b.Build(); + + var circuitBreaker = new RepeatedFailuresOverTimeCircuitBreaker("TimeoutStorageConnectivity", + waitTime, + ex => criticalError.Raise("Repeated failures when fetching timeouts from storage, endpoint will be terminated.", ex)); + + return new ExpiredTimeoutsPoller(b.Build(), b.Build(), dispatcherAddress, circuitBreaker, () => DateTime.UtcNow); + }, DependencyLifecycle.SingleInstance); + + context.RegisterStartupTask(b => new TimeoutPollerRunner(b.Build())); + } + + static string SetupDispatcherSatellite(FeatureConfigurationContext context, TransportTransactionMode requiredTransactionSupport) + { + var satelliteLogicalAddress = context.Settings.LogicalAddress().CreateQualifiedAddress("TimeoutsDispatcher"); + var satelliteAddress = context.Settings.GetTransportAddress(satelliteLogicalAddress); + + context.AddSatelliteReceiver("Timeout Dispatcher Processor", satelliteAddress, requiredTransactionSupport, PushRuntimeSettings.Default, RecoverabilityPolicy, + (builder, pushContext) => + { + var dispatchBehavior = new DispatchTimeoutBehavior( + builder.Build(), + builder.Build(), + requiredTransactionSupport); + + return dispatchBehavior.Invoke(pushContext); + }); + + return satelliteAddress; + } + + static void SetupStorageSatellite(FeatureConfigurationContext context, TransportTransactionMode requiredTransactionSupport) + { + var satelliteLogicalAddress = context.Settings.LogicalAddress().CreateQualifiedAddress("Timeouts"); + var satelliteAddress = context.Settings.GetTransportAddress(satelliteLogicalAddress); + + context.AddSatelliteReceiver("Timeout Message Processor", satelliteAddress, requiredTransactionSupport, PushRuntimeSettings.Default, RecoverabilityPolicy, + (builder, pushContext) => + { + var storeBehavior = new StoreTimeoutBehavior( + builder.Build(), + builder.Build(), + builder.Build(), + context.Settings.EndpointName().ToString()); + + return storeBehavior.Invoke(pushContext); + }); + + context.Settings.Get().Set(satelliteAddress); + } + + static bool HasAlternateTimeoutManagerBeenConfigured(ReadOnlySettings settings) + { + return settings.Get().TransportAddress != null; + } + + static RecoverabilityAction RecoverabilityPolicy(RecoverabilityConfig config, ErrorContext errorContext) + { + if (errorContext.ImmediateProcessingFailures <= MaxNumberOfImmediateRetries) + { + return RecoverabilityAction.ImmediateRetry(); + } + + return RecoverabilityAction.MoveToError(config.Failed.ErrorQueue); + } + + const int MaxNumberOfImmediateRetries = 4; + TimeSpan TimeToWaitBeforeTriggeringCriticalError = TimeSpan.FromMinutes(2); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/Core/TimeoutManagerHeaders.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManagerHeaders.cs similarity index 80% rename from src/NServiceBus.Core/Timeout/Core/TimeoutManagerHeaders.cs rename to src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManagerHeaders.cs index 96dbc041cec..f35482ef519 100644 --- a/src/NServiceBus.Core/Timeout/Core/TimeoutManagerHeaders.cs +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutManagerHeaders.cs @@ -1,6 +1,6 @@ -namespace NServiceBus.Timeout +namespace NServiceBus { - class TimeoutManagerHeaders + static class TimeoutManagerHeaders { public const string Expire = "NServiceBus.Timeout.Expire"; public const string RouteExpiredTimeoutTo = "NServiceBus.Timeout.RouteExpiredTimeoutTo"; diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutPollerRunner.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutPollerRunner.cs new file mode 100644 index 00000000000..3ecccf42353 --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManager/TimeoutPollerRunner.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Features +{ + using System.Threading.Tasks; + + class TimeoutPollerRunner : FeatureStartupTask + { + public TimeoutPollerRunner(ExpiredTimeoutsPoller poller) + { + this.poller = poller; + } + + protected override Task OnStart(IMessageSession session) + { + poller.Start(); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return poller.Stop(); + } + + ExpiredTimeoutsPoller poller; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerAddressConfiguration.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerAddressConfiguration.cs new file mode 100644 index 00000000000..f2d90c81a9e --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerAddressConfiguration.cs @@ -0,0 +1,24 @@ +namespace NServiceBus +{ + using System; + + class TimeoutManagerAddressConfiguration + { + internal TimeoutManagerAddressConfiguration(string defaultTimeoutManagerAddress) + { + TransportAddress = defaultTimeoutManagerAddress; + } + + public string TransportAddress { get; private set; } + + public void Set(string newTimeoutManagerAddress) + { + Guard.AgainstNullAndEmpty(newTimeoutManagerAddress, "newTimeoutManagerAddress"); + if (TransportAddress != null) + { + throw new InvalidOperationException("Another feature or the UnicastBusConfig section has already set the timeout manager address."); + } + TransportAddress = newTimeoutManagerAddress; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerRoutingStrategy.cs b/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerRoutingStrategy.cs new file mode 100644 index 00000000000..94369626d6f --- /dev/null +++ b/src/NServiceBus.Core/DelayedDelivery/TimeoutManagerRoutingStrategy.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Routing; + + class TimeoutManagerRoutingStrategy : RoutingStrategy + { + string timeoutManagerAddress; + string ultimateDestination; + DateTime deliverAt; + + public TimeoutManagerRoutingStrategy(string timeoutManagerAddress, string ultimateDestination, DateTime deliverAt) + { + this.ultimateDestination = ultimateDestination; + this.deliverAt = deliverAt; + this.timeoutManagerAddress = timeoutManagerAddress; + } + + public override AddressTag Apply(Dictionary headers) + { + headers[TimeoutManagerHeaders.RouteExpiredTimeoutTo] = ultimateDestination; + headers[TimeoutManagerHeaders.Expire] = DateTimeExtensions.ToWireFormattedString(deliverAt); + + return new UnicastAddressTag(timeoutManagerAddress); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraint.cs b/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraint.cs new file mode 100644 index 00000000000..008f571658d --- /dev/null +++ b/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraint.cs @@ -0,0 +1,12 @@ +namespace NServiceBus.DeliveryConstraints +{ + using System.Collections.Generic; + + /// + /// Base class for delivery constraints. + /// + public abstract class DeliveryConstraint + { + internal static List EmptyConstraints = new List(0); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraintContextExtensions.cs b/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraintContextExtensions.cs new file mode 100644 index 00000000000..05c114d13b5 --- /dev/null +++ b/src/NServiceBus.Core/DeliveryConstraints/DeliveryConstraintContextExtensions.cs @@ -0,0 +1,115 @@ +namespace NServiceBus.DeliveryConstraints +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Extensibility; + using Settings; + using Transport; + + /// + /// Gives access to s that exist in the various s. + /// + public static class DeliveryConstraintContextExtensions + { + /// + /// Adds a to a . + /// + public static void AddDeliveryConstraint(this ContextBag context, DeliveryConstraint constraint) + { + List constraints; + + if (!context.TryGet(out constraints)) + { + constraints = new List(); + + context.Set(constraints); + } + + if (constraints.Any(c => c.GetType() == constraint.GetType())) + { + throw new InvalidOperationException("Constraint of type " + constraint.GetType().FullName + " already exists"); + } + + constraints.Add(constraint); + } + + /// + /// Tries to retrieves an instance of from a . + /// + public static bool TryGetDeliveryConstraint(this ContextBag context, out T constraint) where T : DeliveryConstraint + { + List constraints; + + if (context.TryGet(out constraints)) + { + return constraints.TryGet(out constraint); + } + constraint = null; + return false; + } + + /// + /// Tries to remove an instance of from a . + /// + public static bool TryRemoveDeliveryConstraint(this ContextBag context, out T constraint) where T : DeliveryConstraint + { + List constraints; + + if (context.TryGet(out constraints)) + { + var result = constraints.TryGet(out constraint); + if (result) + { + constraints.Remove(constraint); + } + return result; + } + constraint = null; + return false; + } + + /// + /// Removes a to a . + /// + public static List GetDeliveryConstraints(this ContextBag context) + { + List constraints; + + if (context.TryGet(out constraints)) + { + return constraints; + } + + return new List(); + } + + /// + /// Removes a to a . + /// + public static void RemoveDeliveryConstaint(this ContextBag context, DeliveryConstraint constraint) + { + List constraints; + + if (!context.TryGet(out constraints)) + { + return; + } + + constraints.Remove(constraint); + } + + internal static bool TryGet(this List list, out T constraint) where T : DeliveryConstraint + { + constraint = list.OfType().FirstOrDefault(); + + return constraint != null; + } + + internal static bool DoesTransportSupportConstraint(this ReadOnlySettings settings) where T : DeliveryConstraint + { + return settings.Get() + .DeliveryConstraints.Any(t => typeof(T).IsAssignableFrom(t)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/DependencyLifecycle.cs b/src/NServiceBus.Core/DependencyLifecycle.cs index b2faabd8f5b..0bd52961775 100644 --- a/src/NServiceBus.Core/DependencyLifecycle.cs +++ b/src/NServiceBus.Core/DependencyLifecycle.cs @@ -6,18 +6,18 @@ namespace NServiceBus public enum DependencyLifecycle { /// - /// The same instance will be returned each time + /// The same instance will be returned each time. /// SingleInstance, /// - /// The instance will be singleton for the duration of the unit of work. In practice this means - /// the processing of a single transport message + /// The instance will be singleton for the duration of the unit of work. In practice this means + /// the processing of a single transport message. /// InstancePerUnitOfWork, /// - /// A new instance will be returned fro each call + /// A new instance will be returned for each call. /// InstancePerCall } diff --git a/src/NServiceBus.Core/DetectObsoleteConfigurationSettings.cs b/src/NServiceBus.Core/DetectObsoleteConfigurationSettings.cs new file mode 100644 index 00000000000..68f15222306 --- /dev/null +++ b/src/NServiceBus.Core/DetectObsoleteConfigurationSettings.cs @@ -0,0 +1,55 @@ +namespace NServiceBus +{ + using System; + using Config; + using Features; + + class DetectObsoleteConfigurationSettings : Feature + { + public DetectObsoleteConfigurationSettings() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var unicastBusConfig = context.Settings.GetConfigSection(); + + if (!string.IsNullOrWhiteSpace(unicastBusConfig?.ForwardReceivedMessagesTo)) + { + throw new NotSupportedException($"The {nameof(UnicastBusConfig.ForwardReceivedMessagesTo)} attribute in the {nameof(UnicastBusConfig)} configuration section is no longer supported. Switch to the code API by using `{nameof(EndpointConfiguration)}.ForwardReceivedMessagesTo` instead."); + } + + if (!string.IsNullOrWhiteSpace(unicastBusConfig?.DistributorControlAddress)) + { + throw new NotSupportedException($"The {nameof(UnicastBusConfig.DistributorControlAddress)} attribute in the {nameof(UnicastBusConfig)} configuration section is no longer supported. Remove this from the configuration section. Switch to the code API by using `{nameof(EndpointConfiguration)}.EnlistWithLegacyMSMQDistributor` instead."); + } + + if (!string.IsNullOrWhiteSpace(unicastBusConfig?.DistributorDataAddress)) + { + throw new NotSupportedException($"The {nameof(UnicastBusConfig.DistributorDataAddress)} attribute in the {nameof(UnicastBusConfig)} configuration section is no longer supported. Remove this from the configuration section. Switch to the code API by using `{nameof(EndpointConfiguration)}.EnlistWithLegacyMSMQDistributor` instead."); + } + + var masterNodeConfig = context.Settings.GetConfigSection(); + + if (masterNodeConfig != null) + { + throw new NotSupportedException($"The {nameof(MasterNodeConfig)} configuration section is no longer supported. Remove this from this configuration section. Switch to the code API by using `{nameof(EndpointConfiguration)}.EnlistWithLegacyMSMQDistributor` instead."); + } + + var secondLevelRetriesConfig = context.Settings.GetConfigSection(); + + if (secondLevelRetriesConfig != null) + { + throw new NotSupportedException($"The {nameof(SecondLevelRetriesConfig)} configuration section is no longer supported. Remove this from this configuration section. Switch to the code API by using `endpointConfiguration.Recoverability().Delayed(settings => ...)` instead."); + } + + var transportConfig = context.Settings.GetConfigSection(); + + if (transportConfig != null) + { + throw new NotSupportedException($"The {nameof(TransportConfig)} configuration section is no longer supported. Remove this from this configuration section. Switch to the code API by using `endpointConfiguration.LimitMessageProcessingConcurrencyTo(1)` to change the concurrency level or `endpointConfiguration.Recoverability().Immediate(settings => settings.NumberOfRetries(5)` to change the number of immediate retries instead."); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Distributor/ConfigureDistributor_Obsolete.cs b/src/NServiceBus.Core/Distributor/ConfigureDistributor_Obsolete.cs deleted file mode 100644 index 3f4d07f873b..00000000000 --- a/src/NServiceBus.Core/Distributor/ConfigureDistributor_Obsolete.cs +++ /dev/null @@ -1,40 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx(TreatAsErrorFromVersion = "5.0", RemoveInVersion = "6.0", Message = "The NServiceBus Distributor was moved into its own assembly (NServiceBus.Distributor.MSMQ.dll), please make sure you reference the new assembly.")] - public static class ConfigureDistributor - { - public static bool DistributorEnabled(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static bool DistributorConfiguredToRunOnThisEndpoint(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static bool WorkerRunsOnThisEndpoint(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static Configure RunDistributor(this Configure config, bool withWorker = true) - { - throw new Exception("Obsolete"); - } - - public static Configure RunDistributorWithNoWorkerOnItsEndpoint(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static Configure EnlistWithDistributor(this Configure config) - { - throw new Exception("Obsolete"); - } - } -} diff --git a/src/NServiceBus.Core/Distributor/MasterNode/ConfigureMasterNode_Obsolete.cs b/src/NServiceBus.Core/Distributor/MasterNode/ConfigureMasterNode_Obsolete.cs deleted file mode 100644 index ce1bdf0e0ab..00000000000 --- a/src/NServiceBus.Core/Distributor/MasterNode/ConfigureMasterNode_Obsolete.cs +++ /dev/null @@ -1,35 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx(TreatAsErrorFromVersion = "5.0", RemoveInVersion = "6.0", Message = "The NServiceBus Distributor was moved into its own assembly (NServiceBus.Distributor.MSMQ.dll), please make sure you reference the new assembly.")] - public static class ConfigureMasterNode - { - public static Configure AsMasterNode(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static bool IsConfiguredAsMasterNode(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static bool HasMasterNode(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static string GetMasterNode(this Configure config) - { - throw new Exception("Obsolete"); - } - - public static Address GetMasterNodeAddress(this Configure config) - { - throw new Exception("Obsolete"); - } - } -} diff --git a/src/NServiceBus.Core/Distributor/MasterNode/DefaultMasterNodeAddress.cs b/src/NServiceBus.Core/Distributor/MasterNode/DefaultMasterNodeAddress.cs deleted file mode 100644 index 02b88c41ea6..00000000000 --- a/src/NServiceBus.Core/Distributor/MasterNode/DefaultMasterNodeAddress.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NServiceBus.Distributor.Config -{ - class DefaultMasterNodeAddress : IWantToRunBeforeConfigurationIsFinalized - { - public void Run(Configure config) - { - config.Settings.SetDefault("MasterNode.Address", Address.Parse(config.Settings.EndpointName())); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Distributor/MasterNode/MasterNodeConfig.cs b/src/NServiceBus.Core/Distributor/MasterNode/MasterNodeConfig.cs deleted file mode 100644 index afb50885232..00000000000 --- a/src/NServiceBus.Core/Distributor/MasterNode/MasterNodeConfig.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Config -{ - using System.Configuration; - - /// - /// Configuration section for holding the node which is the master. - /// - public class MasterNodeConfig : ConfigurationSection - { - /// - /// The node . - /// - [ConfigurationProperty("Node", IsRequired = true)] - public string Node - { - get - { - return this["Node"] as string; - } - set - { - this["Node"] = value; - } - } - } -} diff --git a/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService.cs b/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService.cs index 4522a5d50cb..b08ae52fbf9 100644 --- a/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService.cs +++ b/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService.cs @@ -5,28 +5,24 @@ namespace NServiceBus using System.Linq; using System.Text; using Config; - using Encryption.Rijndael; - using NServiceBus.Encryption; - using NServiceBus.Logging; - using NServiceBus.ObjectBuilder; - using NServiceBus.Settings; + using Logging; + using Settings; /// /// Contains extension methods to NServiceBus.Configure. /// public static partial class ConfigureRijndaelEncryptionService { - static readonly ILog Log = LogManager.GetLogger("NServiceBus.Settings.ConfigureRijndaelEncryptionService"); - /// - /// Use 256 bit AES encryption based on the Rijndael cipher. + /// Use 256 bit AES encryption based on the Rijndael cipher. /// - public static void RijndaelEncryptionService(this BusConfiguration config) + /// The instance to apply the settings to. + public static void RijndaelEncryptionService(this EndpointConfiguration config) { - RegisterEncryptionService(config, context => + Guard.AgainstNull(nameof(config), config); + RegisterEncryptionService(config, () => { - var section = context.Build() - .Settings + var section = config.Settings .GetConfigSection(); return ConvertConfigToRijndaelService(section); @@ -49,7 +45,7 @@ internal static void ValidateConfigSection(RijndaelEncryptionServiceConfig secti { if (section == null) { - throw new Exception("No RijndaelEncryptionServiceConfig defined. Please specify a valid 'RijndaelEncryptionServiceConfig' in your application's configuration file."); + throw new Exception("No RijndaelEncryptionServiceConfig defined. Specify a valid 'RijndaelEncryptionServiceConfig' in the application's configuration file."); } if (section.ExpiredKeys == null) { @@ -65,7 +61,7 @@ internal static void ValidateConfigSection(RijndaelEncryptionServiceConfig secti } if (RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section)) { - Log.Warn("The RijndaelEncryptionServiceConfig has a 'ExpiredKeys' property defined however some keys have no 'KeyIdentifier' property value. Please verify if this is intentional."); + Log.Warn("The RijndaelEncryptionServiceConfig has a 'ExpiredKeys' property defined however some keys have no 'KeyIdentifier' property value. Verify if this is intentional."); } if (RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section)) { @@ -73,13 +69,12 @@ internal static void ValidateConfigSection(RijndaelEncryptionServiceConfig secti } if (RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveDuplicateKeys(section)) { - throw new Exception("The RijndaelEncryptionServiceConfig has overlapping ExpiredKeys defined. Please ensure that no keys overlap in the 'ExpiredKeys' property."); + throw new Exception("The RijndaelEncryptionServiceConfig has overlapping ExpiredKeys defined. Ensure that no keys overlap in the 'ExpiredKeys' property."); } if (RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section)) { throw new Exception("The RijndaelEncryptionServiceConfig has duplicate KeyIdentifiers defined with the same key identifier. Key identifiers must be unique in the complete configuration section."); } - } internal static List ExtractDecryptionKeysFromConfigSection(RijndaelEncryptionServiceConfig section) @@ -94,65 +89,41 @@ internal static List ExtractDecryptionKeysFromConfigSection(RijndaelEncr } /// - /// Use 256 bit AES encryption based on the Rijndael cipher. + /// Use 256 bit AES encryption based on the Rijndael cipher. /// - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "6", - Replacement = "RijndaelEncryptionService(string encryptionKeyIdentifier, byte[] encryptionKey, IEnumerable> expiredKeys = null)")] - public static void RijndaelEncryptionService(this BusConfiguration config, string encryptionKey, List expiredKeys = null) - { - if (string.IsNullOrWhiteSpace(encryptionKey)) - { - throw new ArgumentNullException("encryptionKey"); - } - - if (expiredKeys == null) - { - expiredKeys = new List(); - } - else - { - VerifyKeys(expiredKeys); - } - - var decryptionKeys = expiredKeys.ConvertAll(x => ParseKey(x, KeyFormat.Ascii)); - decryptionKeys.Insert(0, ParseKey(encryptionKey, KeyFormat.Ascii)); + /// The instance to apply the settings to. + /// Encryption key identifier. + /// Encryption Key. + /// A list of decryption keys. + public static void RijndaelEncryptionService(this EndpointConfiguration config, string encryptionKeyIdentifier, byte[] encryptionKey, IList decryptionKeys = null) - RegisterEncryptionService(config, context => BuildRijndaelEncryptionService( - null, - new Dictionary(), - decryptionKeys - )); - } - - /// - /// Use 256 bit AES encryption based on the Rijndael cipher. - /// - public static void RijndaelEncryptionService(this BusConfiguration config, string encryptionKeyIdentifier, byte[] encryptionKey, IList decryptionKeys = null) { - if (null == encryptionKeyIdentifier) throw new ArgumentNullException("encryptionKeyIdentifier"); - if (null == encryptionKey) throw new ArgumentNullException("encryptionKey"); + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(encryptionKey), encryptionKey); decryptionKeys = decryptionKeys ?? new List(); - RegisterEncryptionService(config, context => BuildRijndaelEncryptionService( + RegisterEncryptionService(config, () => BuildRijndaelEncryptionService( encryptionKeyIdentifier, - new Dictionary { { encryptionKeyIdentifier, encryptionKey } }, + new Dictionary + { + {encryptionKeyIdentifier, encryptionKey} + }, decryptionKeys)); } /// - /// Use 256 bit AES encryption based on the Rijndael cipher. + /// Use 256 bit AES encryption based on the Rijndael cipher. /// - public static void RijndaelEncryptionService(this BusConfiguration config, string encryptionKeyIdentifier, IDictionary keys, IList decryptionKeys = null) + public static void RijndaelEncryptionService(this EndpointConfiguration config, string encryptionKeyIdentifier, IDictionary keys, IList decryptionKeys = null) { - if (null == encryptionKeyIdentifier) throw new ArgumentNullException("encryptionKeyIdentifier"); - if (null == keys) throw new ArgumentNullException("keys"); + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(encryptionKeyIdentifier), encryptionKeyIdentifier); + Guard.AgainstNull(nameof(keys), keys); decryptionKeys = decryptionKeys ?? new List(); - RegisterEncryptionService(config, context => BuildRijndaelEncryptionService( + RegisterEncryptionService(config, () => BuildRijndaelEncryptionService( encryptionKeyIdentifier, keys, decryptionKeys)); @@ -162,14 +133,14 @@ internal static void VerifyKeys(List expiredKeys) { if (expiredKeys.Count != expiredKeys.Distinct().Count()) { - throw new ArgumentException("Overlapping keys defined. Please ensure that no keys overlap.", "expiredKeys"); + throw new ArgumentException("Overlapping keys defined. Ensure that no keys overlap.", nameof(expiredKeys)); } for (var index = 0; index < expiredKeys.Count; index++) { var encryptionKey = expiredKeys[index]; if (string.IsNullOrWhiteSpace(encryptionKey)) { - throw new ArgumentException(string.Format("Empty encryption key detected in position {0}.", index), "expiredKeys"); + throw new ArgumentException($"Empty encryption key detected in position {index}.", nameof(expiredKeys)); } } } @@ -186,17 +157,25 @@ IList expiredKeys expiredKeys ); } + /// - /// Register a custom to be used for message encryption. + /// Register a custom to be used for message encryption. /// - public static void RegisterEncryptionService(this BusConfiguration config, Func func) + /// The instance to apply the settings to. + /// + /// A delegate that constructs the instance of to use for all + /// encryption. + /// + public static void RegisterEncryptionService(this EndpointConfiguration config, Func func) { - config.Settings.Set("EncryptionServiceConstructor", func); + Guard.AgainstNull(nameof(config), config); + + config.Settings.Set(EncryptedServiceContstructorKey, func); } - internal static bool GetEncryptionServiceConstructor(this ReadOnlySettings settings, out Func func) + internal static Func GetEncryptionServiceConstructor(this ReadOnlySettings settings) { - return settings.TryGet("EncryptionServiceConstructor", out func); + return settings.Get>(EncryptedServiceContstructorKey); } static byte[] ParseKey(string key, KeyFormat keyFormat) @@ -208,10 +187,10 @@ static byte[] ParseKey(string key, KeyFormat keyFormat) case KeyFormat.Base64: return Convert.FromBase64String(key); } - throw new NotSupportedException("Unsupported KeyFormat"); + throw new NotSupportedException("Unsupported KeyFormat. Supported formats are: ASCII and Base64."); } - internal static IDictionary ExtractKeysFromConfigSection(RijndaelEncryptionServiceConfig section) + internal static Dictionary ExtractKeysFromConfigSection(RijndaelEncryptionServiceConfig section) { var result = new Dictionary(); @@ -233,5 +212,9 @@ static void AddKeyIdentifierItems(dynamic item, Dictionary resul result.Add(item.KeyIdentifier, key); } } + + internal const string EncryptedServiceContstructorKey = "EncryptionServiceConstructor"; + + static readonly ILog Log = LogManager.GetLogger(typeof(ConfigureRijndaelEncryptionService)); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService_Obsolete.cs b/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService_Obsolete.cs deleted file mode 100644 index bb68c1ab2e8..00000000000 --- a/src/NServiceBus.Core/Encryption/ConfigureRijndaelEncryptionService_Obsolete.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigureRijndaelEncryptionService - { - [ObsoleteEx( - Message = "Use `configuration.RijndaelEncryptionService()`, where configuration is an instance of type `BusConfiguration`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure RijndaelEncryptionService(this Configure config) - { - throw new NotImplementedException(); - } - - } -} diff --git a/src/NServiceBus.Core/Encryption/DecryptBehavior.cs b/src/NServiceBus.Core/Encryption/DecryptBehavior.cs index 9231dd9b241..b9c9f3a8ba3 100644 --- a/src/NServiceBus.Core/Encryption/DecryptBehavior.cs +++ b/src/NServiceBus.Core/Encryption/DecryptBehavior.cs @@ -1,41 +1,61 @@ namespace NServiceBus { using System; - using NServiceBus.Encryption; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NServiceBus.Unicast.Transport; + using System.Reflection; + using System.Threading.Tasks; + using Pipeline; - class DecryptBehavior : IBehavior + class DecryptBehavior : IBehavior { - EncryptionMutator messageMutator; + public DecryptBehavior(EncryptionInspector messageInspector, IEncryptionService encryptionService) + { + this.messageInspector = messageInspector; + this.encryptionService = encryptionService; + } - public DecryptBehavior(EncryptionMutator messageMutator) + public Task Invoke(IIncomingLogicalMessageContext context, Func next) { - this.messageMutator = messageMutator; + var current = context.Message.Instance; + + foreach (var item in messageInspector.ScanObject(current)) + { + DecryptMember(item.Item1, item.Item2, context); + } + + context.UpdateMessageInstance(current); + + return next(context); } - public void Invoke(IncomingContext context, Action next) + + + void DecryptMember(object target, MemberInfo property, IIncomingLogicalMessageContext context) { - if (context.IncomingLogicalMessage.IsControlMessage()) + var encryptedValue = property.GetValue(target); + + var wireEncryptedString = encryptedValue as WireEncryptedString; + if (wireEncryptedString != null) { - next(); - return; + encryptionService.DecryptValue(wireEncryptedString, context); + } + + var stringToDecrypt = encryptedValue as string; + if (stringToDecrypt != null) + { + encryptionService.DecryptValue(ref stringToDecrypt, context); + property.SetValue(target, stringToDecrypt); } - var current = context.IncomingLogicalMessage.Instance; - current = messageMutator.MutateIncoming(current, context); - context.IncomingLogicalMessage.UpdateMessageInstance(current); - next(); } + IEncryptionService encryptionService; + EncryptionInspector messageInspector; + public class DecryptRegistration : RegisterStep { - public DecryptRegistration() - : base("InvokeDecryption", typeof(DecryptBehavior), "Invokes the decryption logic") + public DecryptRegistration(EncryptionInspector inspector, IEncryptionService encryptionService) + : base("InvokeDecryption", typeof(DecryptBehavior), "Invokes the decryption logic", b => new DecryptBehavior(inspector, encryptionService)) { - InsertAfter(WellKnownStep.ExecuteLogicalMessages); - InsertBefore(WellKnownStep.MutateIncomingMessages); + InsertBefore("MutateIncomingMessages"); } - } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/EncryptBehavior.cs b/src/NServiceBus.Core/Encryption/EncryptBehavior.cs index abf508c29be..bd49959fc4d 100644 --- a/src/NServiceBus.Core/Encryption/EncryptBehavior.cs +++ b/src/NServiceBus.Core/Encryption/EncryptBehavior.cs @@ -1,43 +1,65 @@ namespace NServiceBus { using System; - using NServiceBus.Encryption; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NServiceBus.Unicast.Transport; + using System.Reflection; + using System.Threading.Tasks; + using Pipeline; - class EncryptBehavior : IBehavior + class EncryptBehavior : IBehavior { - EncryptionMutator messageMutator; + public EncryptBehavior(EncryptionInspector messageInspector, IEncryptionService encryptionService) + { + this.messageInspector = messageInspector; + this.encryptionService = encryptionService; + } - public EncryptBehavior(EncryptionMutator messageMutator) + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) { - this.messageMutator = messageMutator; + var currentMessageToSend = context.Message.Instance; + + foreach (var item in messageInspector.ScanObject(currentMessageToSend)) + { + EncryptMember(item.Item1, item.Item2, context); + } + + context.UpdateMessage(currentMessageToSend); + + return next(context); } - public void Invoke(OutgoingContext context, Action next) + void EncryptMember(object message, MemberInfo member, IOutgoingLogicalMessageContext context) { - if (context.OutgoingLogicalMessage.IsControlMessage()) + var valueToEncrypt = member.GetValue(message); + + var wireEncryptedString = valueToEncrypt as WireEncryptedString; + if (wireEncryptedString != null) { - next(); + encryptionService.EncryptValue(wireEncryptedString, context); return; } - var currentMessageToSend = context.OutgoingLogicalMessage.Instance; - currentMessageToSend = messageMutator.MutateOutgoing(currentMessageToSend, context); - context.OutgoingLogicalMessage.UpdateMessageInstance(currentMessageToSend); - next(); + var stringToEncrypt = valueToEncrypt as string; + if (stringToEncrypt != null) + { + encryptionService.EncryptValue(ref stringToEncrypt, context); + + member.SetValue(message, stringToEncrypt); + return; + } + + throw new Exception("Only string properties is supported for convention based encryption, check the configured conventions."); } + IEncryptionService encryptionService; + EncryptionInspector messageInspector; + public class EncryptRegistration : RegisterStep { - public EncryptRegistration() - : base("InvokeEncryption", typeof(EncryptBehavior), "Invokes the encryption logic") + public EncryptRegistration(EncryptionInspector inspector, IEncryptionService encryptionService) + : base("InvokeEncryption", typeof(EncryptBehavior), "Invokes the encryption logic", b => new EncryptBehavior(inspector, encryptionService)) { - InsertAfter(WellKnownStep.MutateOutgoingMessages); - InsertBefore(WellKnownStep.CreatePhysicalMessage); + InsertAfter("MutateOutgoingMessages"); } - } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/Encryption.cs b/src/NServiceBus.Core/Encryption/Encryption.cs new file mode 100644 index 00000000000..ae01a2d7ea9 --- /dev/null +++ b/src/NServiceBus.Core/Encryption/Encryption.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.Features +{ + class Encryption : Feature + { + public Encryption() + { + Prerequisite(c => c.Settings.HasSetting(ConfigureRijndaelEncryptionService.EncryptedServiceContstructorKey), "Encryption service not defined."); + + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var serviceConstructor = context.Settings.GetEncryptionServiceConstructor(); + var service = serviceConstructor(); + var inspector = new EncryptionInspector(context.Settings.Get()); + + context.Pipeline.Register(new EncryptBehavior.EncryptRegistration(inspector, service)); + context.Pipeline.Register(new DecryptBehavior.DecryptRegistration(inspector, service)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/EncryptionInspector.cs b/src/NServiceBus.Core/Encryption/EncryptionInspector.cs new file mode 100644 index 00000000000..a4da192e5b5 --- /dev/null +++ b/src/NServiceBus.Core/Encryption/EncryptionInspector.cs @@ -0,0 +1,166 @@ +// ReSharper disable ReturnTypeCanBeEnumerable.Local +namespace NServiceBus +{ + using System; + using System.Collections; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Reflection; + + class EncryptionInspector + { + public EncryptionInspector(Conventions conventions) + { + this.conventions = conventions; + } + + static bool IsIndexedProperty(MemberInfo member) + { + var propertyInfo = member as PropertyInfo; + + return propertyInfo?.GetIndexParameters().Length > 0; + } + + bool IsEncryptedMember(MemberInfo arg) + { + var propertyInfo = arg as PropertyInfo; + if (propertyInfo != null) + { + if (propertyInfo.GetIndexParameters().Length > 0) + { + if (conventions.IsEncryptedProperty(propertyInfo)) + { + throw new Exception("Cannot encrypt or decrypt indexed properties that return a WireEncryptedString."); + } + + return false; + } + + return conventions.IsEncryptedProperty(propertyInfo); + } + + var fieldInfo = arg as FieldInfo; + if (fieldInfo != null) + { + return fieldInfo.FieldType == typeof(WireEncryptedString); + } + + return false; + } + + public List> ScanObject(object root) + { + var visitedMembers = new HashSet(); + return ScanObject(root, visitedMembers); + } + + List> ScanObject(object root, HashSet visitedMembers) + { + if (root == null || visitedMembers.Contains(root)) + { + return AlreadyVisited; + } + + visitedMembers.Add(root); + + var members = GetFieldsAndProperties(root); + + var properties = new List>(); + + foreach (var member in members) + { + if (IsEncryptedMember(member) && member.GetValue(root) != null) + { + var value = member.GetValue(root); + if (value is string || value is WireEncryptedString) + { + properties.Add(Tuple.Create(root, member)); + continue; + } + throw new Exception("Only string properties are supported for convention based encryption. Check the configured conventions."); + } + + //don't recurse over primitives or system types + if (member.ReflectedType.IsPrimitive || member.ReflectedType.IsSystemType()) + { + continue; + } + + // don't try to recurse over members of WireEncryptedString + if (member.DeclaringType == typeof(WireEncryptedString)) + { + continue; + } + + if (IsIndexedProperty(member)) + { + continue; + } + + var child = member.GetValue(root); + + var items = child as IEnumerable; + if (items != null) + { + foreach (var item in items) + { + if (item == null) + { + continue; + } + + //don't recurse over primitives or system types + if (item.GetType().IsPrimitive || item.GetType().IsSystemType()) + { + break; + } + + properties.AddRange(ScanObject(item, visitedMembers)); + } + } + else + { + properties.AddRange(ScanObject(child, visitedMembers)); + } + } + return properties; + } + + static List GetFieldsAndProperties(object target) + { + if (target == null) + { + return NoMembers; + } + + return cache.GetOrAdd(target.GetType().TypeHandle, typeHandle => + { + var messageType = Type.GetTypeFromHandle(typeHandle); + var members = new List(); + foreach (var member in messageType.GetMembers(BindingFlags.Public | BindingFlags.Instance)) + { + var fieldInfo = member as FieldInfo; + if (fieldInfo != null && !fieldInfo.IsInitOnly) + { + members.Add(fieldInfo); + } + + var propInfo = member as PropertyInfo; + if (propInfo != null && propInfo.CanWrite) + { + members.Add(propInfo); + } + } + return members; + }); + } + + Conventions conventions; + + static List NoMembers = new List(0); + + static List> AlreadyVisited = new List>(0); + + static ConcurrentDictionary> cache = new ConcurrentDictionary>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/EncryptionMutator.cs b/src/NServiceBus.Core/Encryption/EncryptionMutator.cs deleted file mode 100644 index 99632a41424..00000000000 --- a/src/NServiceBus.Core/Encryption/EncryptionMutator.cs +++ /dev/null @@ -1,301 +0,0 @@ -namespace NServiceBus.Encryption -{ - using System; - using System.Collections; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Logging; - using NServiceBus.Pipeline.Contexts; - using Utils.Reflection; - - class EncryptionMutator - { - public EncryptionMutator(IEncryptionService encryptionService, Conventions conventions) - { - this.encryptionService = encryptionService; - encryptionServiceWithContext = encryptionService as IEncryptionServiceWithContext; - this.conventions = conventions; - } - - - public object MutateOutgoing(object message, OutgoingContext outgoingContext) - { - this.outgoingContext = outgoingContext; - ForEachMember( - message, - EncryptMember, - IsEncryptedMember - ); - - return message; - } - - public object MutateIncoming(object message, IncomingContext incomingContext) - { - this.incomingContext = incomingContext; - - ForEachMember( - message, - DecryptMember, - IsEncryptedMember - ); - - return message; - } - - static bool IsIndexedProperty(MemberInfo member) - { - var propertyInfo = member as PropertyInfo; - - if (propertyInfo != null) - { - return propertyInfo.GetIndexParameters().Length > 0; - } - - return false; - } - - bool IsEncryptedMember(MemberInfo arg) - { - var propertyInfo = arg as PropertyInfo; - if (propertyInfo != null) - { - if (propertyInfo.GetIndexParameters().Length > 0) - { - if (conventions.IsEncryptedProperty(propertyInfo)) - { - throw new Exception("Cannot encrypt or decrypt indexed properties that return a WireEncryptedString."); - } - - return false; - } - - return conventions.IsEncryptedProperty(propertyInfo); - } - - var fieldInfo = arg as FieldInfo; - if (fieldInfo != null) - { - return fieldInfo.FieldType == typeof(WireEncryptedString); - } - - return false; - } - - void ForEachMember(object root, Action action, Func appliesTo) - { - if (root == null || visitedMembers.Contains(root)) - { - return; - } - - visitedMembers.Add(root); - - var members = GetFieldsAndProperties(root); - - foreach (var member in members) - { - if (appliesTo(member)) - { - action(root, member); - } - - //don't recurse over primitives or system types - if (member.ReflectedType.IsPrimitive || member.ReflectedType.IsSystemType()) - { - continue; - } - - if (IsIndexedProperty(member)) - { - continue; - } - - var child = member.GetValue(root); - - var items = child as IEnumerable; - if (items != null) - { - foreach (var item in items) - { - if (item == null) - { - continue; - } - - //don't recurse over primitives or system types - if (item.GetType().IsPrimitive || item.GetType().IsSystemType()) - { - break; - } - - ForEachMember(item, action, appliesTo); - } - } - else - { - ForEachMember(child, action, appliesTo); - } - } - } - - void EncryptMember(object target, MemberInfo member) - { - var valueToEncrypt = member.GetValue(target); - - if (valueToEncrypt == null) - { - return; - } - - var wireEncryptedString = valueToEncrypt as WireEncryptedString; - if (wireEncryptedString != null) - { - var encryptedString = wireEncryptedString; - EncryptWireEncryptedString(encryptedString); - - //we clear the properties to avoid having the extra data serialized - encryptedString.EncryptedBase64Value = null; - encryptedString.Base64Iv = null; - } - else - { - member.SetValue(target, EncryptUserSpecifiedProperty(valueToEncrypt)); - } - - Log.Debug(member.Name + " encrypted successfully"); - } - - void DecryptMember(object target, MemberInfo property) - { - var encryptedValue = property.GetValue(target); - - if (encryptedValue == null) - { - return; - } - - var wireEncryptedString = encryptedValue as WireEncryptedString; - if (wireEncryptedString != null) - { - Decrypt(wireEncryptedString); - } - else - { - property.SetValue(target, DecryptUserSpecifiedProperty(encryptedValue)); - } - - Log.Debug(property.Name + " decrypted successfully"); - } - - string DecryptUserSpecifiedProperty(object encryptedValue) - { - var stringToDecrypt = encryptedValue as string; - - if (stringToDecrypt == null) - { - throw new Exception("Only string properties is supported for convention based encryption, please check your convention"); - } - - var parts = stringToDecrypt.Split(new[] { '@' }, StringSplitOptions.None); - - return Decrypt(new EncryptedValue - { - EncryptedBase64Value = parts[0], - Base64Iv = parts[1] - }); - } - - void Decrypt(WireEncryptedString encryptedValue) - { - if (encryptedValue.EncryptedValue == null) - { - throw new Exception("Encrypted property is missing encryption data"); - } - - encryptedValue.Value = Decrypt(encryptedValue.EncryptedValue); - } - - string EncryptUserSpecifiedProperty(object valueToEncrypt) - { - var stringToEncrypt = valueToEncrypt as string; - - if (stringToEncrypt == null) - { - throw new Exception("Only string properties is supported for convention based encryption, please check your convention"); - } - - var encryptedValue = Encrypt(stringToEncrypt); - - return string.Format("{0}@{1}", encryptedValue.EncryptedBase64Value, encryptedValue.Base64Iv); - } - - void EncryptWireEncryptedString(WireEncryptedString wireEncryptedString) - { - wireEncryptedString.EncryptedValue = Encrypt(wireEncryptedString.Value); - wireEncryptedString.Value = null; - } - - static IEnumerable GetFieldsAndProperties(object target) - { - if (target == null) - { - return new List(); - } - - var messageType = target.GetType(); - - IEnumerable members; - if (!cache.TryGetValue(messageType, out members)) - { - cache[messageType] = members = messageType.GetMembers(BindingFlags.Public | BindingFlags.Instance) - .Where(m => - { - var fieldInfo = m as FieldInfo; - if (fieldInfo != null) - { - return !fieldInfo.IsInitOnly; - } - - var propInfo = m as PropertyInfo; - if (propInfo != null) - { - return propInfo.CanWrite; - } - - return false; - }) - .ToList(); - } - - return members; - } - - string Decrypt(EncryptedValue value) - { - if (encryptionServiceWithContext != null) - return encryptionServiceWithContext.Decrypt(value, incomingContext); - else - return encryptionService.Decrypt(value); - } - - EncryptedValue Encrypt(string value) - { - if (encryptionServiceWithContext != null) - return encryptionServiceWithContext.Encrypt(value, outgoingContext); - else - return encryptionService.Encrypt(value); - } - - static ConcurrentDictionary> cache = new ConcurrentDictionary>(); - static ILog Log = LogManager.GetLogger(); - readonly HashSet visitedMembers = new HashSet(); - readonly IEncryptionService encryptionService; - readonly IEncryptionServiceWithContext encryptionServiceWithContext; - readonly Conventions conventions; - OutgoingContext outgoingContext; - IncomingContext incomingContext; - } -} diff --git a/src/NServiceBus.Core/Encryption/EncryptionService.cs b/src/NServiceBus.Core/Encryption/EncryptionService.cs deleted file mode 100644 index 2af11d2f21a..00000000000 --- a/src/NServiceBus.Core/Encryption/EncryptionService.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus.Encryption.Rijndael -{ - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0", - Message = "The Rijndael encryption functionality was an internal implementation detail of NServicebus as such it has been removed from the public API.")] - public class EncryptionService - { - } -} diff --git a/src/NServiceBus.Core/Encryption/Encryptor.cs b/src/NServiceBus.Core/Encryption/Encryptor.cs deleted file mode 100644 index 668cecfe821..00000000000 --- a/src/NServiceBus.Core/Encryption/Encryptor.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Text; - using NServiceBus.Encryption; - using NServiceBus.Logging; - using NServiceBus.ObjectBuilder; - - /// - /// Used to configure encryption. - /// - public class Encryptor:Feature - { - Func serviceConstructor; - - internal Encryptor() - { - EnableByDefault(); - Prerequisite(VerifyPrerequisite, "No encryption properties was found in available messages"); - } - - bool VerifyPrerequisite(FeatureConfigurationContext context) - { - var encryptedProperties = GetEncryptedProperties(context); - var encryptionServiceConstructorDefined = context.Settings.GetEncryptionServiceConstructor(out serviceConstructor); - var encryptionPropertiesFound = encryptedProperties.Any(); - if (encryptionPropertiesFound) - { - if (!encryptionServiceConstructorDefined) - { - var stringBuilder = new StringBuilder("Encrypted properties were found but no encryption service has been defined. Please call ConfigurationBuilder.RijndaelEncryptionService or ConfigurationBuilder.RegisterEncryptionService. Encrypted properties: "); - foreach (var encryptedProperty in encryptedProperties) - { - stringBuilder.AppendFormat("{0}.{1}\r\n", encryptedProperty.DeclaringType, encryptedProperty.Name); - } - throw new Exception(stringBuilder.ToString()); - } - } - else - { - if (encryptionServiceConstructorDefined) - { - var message = -@"You have configured a encryption service via either ConfigurationBuilder.RijndaelEncryptionService or ConfigurationBuilder.RegisterEncryptionService however no properties were found on type that require encryption. -Perhaps you forgot to define your encryption message conventions or to define message properties using as WireEncryptedString."; - log.Warn(message); - } - } - return encryptionPropertiesFound; - } - - static List GetEncryptedProperties(FeatureConfigurationContext context) - { - var conventions = context.Settings.Get(); - return context.Settings.GetAvailableTypes() - .SelectMany(messageType => messageType.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - .Where(conventions.IsEncryptedProperty) - .ToList(); - } - - /// - /// - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(serviceConstructor, DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall); - - context.Pipeline.Register(); - context.Pipeline.Register(); - } - static ILog log = LogManager.GetLogger(); - } -} - diff --git a/src/NServiceBus.Core/Encryption/IEncryptionService.cs b/src/NServiceBus.Core/Encryption/IEncryptionService.cs index 01b976ebf69..2b94c0deef9 100644 --- a/src/NServiceBus.Core/Encryption/IEncryptionService.cs +++ b/src/NServiceBus.Core/Encryption/IEncryptionService.cs @@ -1,5 +1,7 @@ -namespace NServiceBus.Encryption +namespace NServiceBus { + using Pipeline; + /// /// Abstraction for encryption capabilities. /// @@ -8,11 +10,11 @@ public interface IEncryptionService /// /// Encrypts the given value returning an EncryptedValue. /// - EncryptedValue Encrypt(string value); + EncryptedValue Encrypt(string value, IOutgoingLogicalMessageContext context); /// /// Decrypts the given EncryptedValue object returning the source string. /// - string Decrypt(EncryptedValue encryptedValue); + string Decrypt(EncryptedValue encryptedValue, IIncomingLogicalMessageContext context); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/IEncryptionServiceWithContext.cs b/src/NServiceBus.Core/Encryption/IEncryptionServiceWithContext.cs deleted file mode 100644 index cb692ebed6e..00000000000 --- a/src/NServiceBus.Core/Encryption/IEncryptionServiceWithContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus.Encryption -{ - using NServiceBus.Pipeline.Contexts; - - interface IEncryptionServiceWithContext : IEncryptionService - { - /// - /// Encrypts the given value returning an EncryptedValue. - /// - EncryptedValue Encrypt(string value, OutgoingContext context); - - /// - /// Decrypts the given EncryptedValue object returning the source string. - /// - string Decrypt(EncryptedValue encryptedValue, IncomingContext context); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/KeyFormat.cs b/src/NServiceBus.Core/Encryption/KeyFormat.cs index 209f40d387d..ea57c97a9f4 100644 --- a/src/NServiceBus.Core/Encryption/KeyFormat.cs +++ b/src/NServiceBus.Core/Encryption/KeyFormat.cs @@ -9,6 +9,7 @@ public enum KeyFormat /// Key is specified as in ASCII characters. /// Ascii = 0, + /// /// Key is specified as a Base64 encoded string. /// diff --git a/src/NServiceBus.Core/Encryption/MemberInfoExtensions.cs b/src/NServiceBus.Core/Encryption/MemberInfoExtensions.cs index c981e6f4c69..d617ddbfdc7 100644 --- a/src/NServiceBus.Core/Encryption/MemberInfoExtensions.cs +++ b/src/NServiceBus.Core/Encryption/MemberInfoExtensions.cs @@ -1,8 +1,7 @@ -namespace NServiceBus.Encryption +namespace NServiceBus { using System; using System.Reflection; - using Utils.Reflection; static class MemberInfoExtensions { @@ -17,7 +16,7 @@ public static object GetValue(this MemberInfo member, object source) } var propertyInfo = (PropertyInfo) member; - + if (!propertyInfo.CanRead) { if (propertyInfo.PropertyType.IsValueType) diff --git a/src/NServiceBus.Core/Encryption/RijndaelEncryptionService.cs b/src/NServiceBus.Core/Encryption/RijndaelEncryptionService.cs index 4e919e76ebd..4cabb6e9a18 100644 --- a/src/NServiceBus.Core/Encryption/RijndaelEncryptionService.cs +++ b/src/NServiceBus.Core/Encryption/RijndaelEncryptionService.cs @@ -1,3 +1,4 @@ +// ReSharper disable CommentTypo //https://github.com/hibernating-rhinos/rhino-esb/blob/master/license.txt //Copyright (c) 2005 - 2009 Ayende Rahien (ayende@ayende.com) //All rights reserved. @@ -24,23 +25,19 @@ //CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, //OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF //THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -namespace NServiceBus.Encryption.Rijndael + +namespace NServiceBus { using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Security.Cryptography; - using NServiceBus.Logging; - using NServiceBus.Pipeline.Contexts; + using Logging; + using Pipeline; - class RijndaelEncryptionService : IEncryptionServiceWithContext + class RijndaelEncryptionService : IEncryptionService { - static readonly ILog Log = LogManager.GetLogger(); - readonly string encryptionKeyIdentifier; - byte[] encryptionKey; - IDictionary keys; - IList decryptionKeys; // Required, as we decrypt in the configured order. - public RijndaelEncryptionService( string encryptionKeyIdentifier, IDictionary keys, @@ -53,11 +50,11 @@ IList decryptionKeys if (string.IsNullOrEmpty(encryptionKeyIdentifier)) { - Log.Error("No encryption key identifier configured. Messages with encrypted properties will fail to send. Please add an encryption key identifier to the rijndael encryption service configuration."); + Log.Error("No encryption key identifier configured. Messages with encrypted properties will fail to send. Add an encryption key identifier to the rijndael encryption service configuration."); } else if (!keys.TryGetValue(encryptionKeyIdentifier, out encryptionKey)) { - throw new ArgumentException("No encryption key for given encryption key identifier.", "encryptionKeyIdentifier"); + throw new ArgumentException("No encryption key for given encryption key identifier.", nameof(encryptionKeyIdentifier)); } else { @@ -67,18 +64,54 @@ IList decryptionKeys VerifyExpiredKeys(decryptionKeys); } - public string Decrypt(EncryptedValue encryptedValue, IncomingContext incomingContext) + public string Decrypt(EncryptedValue encryptedValue, IIncomingLogicalMessageContext context) { string keyIdentifier; - if (TryGetKeyIdentifierHeader(out keyIdentifier, incomingContext)) + if (TryGetKeyIdentifierHeader(out keyIdentifier, context)) { return DecryptUsingKeyIdentifier(encryptedValue, keyIdentifier); } - else + Log.Warn($"Encrypted message has no '{Headers.RijndaelKeyIdentifier}' header. Possibility of data corruption. Upgrade endpoints that send message with encrypted properties."); + return DecryptUsingAllKeys(encryptedValue); + } + + public EncryptedValue Encrypt(string value, IOutgoingLogicalMessageContext context) + { + if (string.IsNullOrEmpty(encryptionKeyIdentifier)) + { + throw new InvalidOperationException("It is required to set the rijndael key identifier."); + } + + AddKeyIdentifierHeader(context); + + using (var rijndael = new RijndaelManaged()) { - Log.WarnFormat("Encrypted message has no '" + Headers.RijndaelKeyIdentifier + "' header. Possibility of data corruption. Please upgrade endpoints that send message with encrypted properties."); - return DecryptUsingAllKeys(encryptedValue); + rijndael.Key = encryptionKey; + rijndael.Mode = CipherMode.CBC; + ConfigureIV(rijndael); + + using (var encryptor = rijndael.CreateEncryptor()) + { + using (var memoryStream = new MemoryStream()) + { + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + using (var writer = new StreamWriter(cryptoStream)) + { + writer.Write(value); + writer.Flush(); + cryptoStream.Flush(); + cryptoStream.FlushFinalBlock(); + return new EncryptedValue + { + EncryptedBase64Value = Convert.ToBase64String(memoryStream.ToArray()), + Base64Iv = Convert.ToBase64String(rijndael.IV) + }; + } + } + } + } } } @@ -88,7 +121,7 @@ string DecryptUsingKeyIdentifier(EncryptedValue encryptedValue, string keyIdenti if (!keys.TryGetValue(keyIdentifier, out decryptionKey)) { - throw new InvalidOperationException("Decryption key not available for key identifier '" + keyIdentifier + "'. Please add this key to the rijndael encryption service configuration. Key identifiers are case sensitive."); + throw new InvalidOperationException($"Decryption key not available for key identifier '{keyIdentifier}'. Add this key to the rijndael encryption service configuration. Key identifiers are case sensitive."); } try @@ -116,7 +149,7 @@ string DecryptUsingAllKeys(EncryptedValue encryptedValue) cryptographicExceptions.Add(exception); } } - var message = string.Format("Could not decrypt message. Tried {0} keys.", decryptionKeys.Count); + var message = $"Could not decrypt message. Tried {decryptionKeys.Count} keys."; throw new AggregateException(message, cryptographicExceptions); } @@ -129,44 +162,17 @@ static string Decrypt(EncryptedValue encryptedValue, byte[] key) rijndael.Mode = CipherMode.CBC; rijndael.Key = key; using (var decryptor = rijndael.CreateDecryptor()) - using (var memoryStream = new MemoryStream(encrypted)) - using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) - using (var reader = new StreamReader(cryptoStream)) - { - return reader.ReadToEnd(); - } - } - } - - public EncryptedValue Encrypt(string value, OutgoingContext outgoingContext) - { - if (string.IsNullOrEmpty(encryptionKeyIdentifier)) - { - throw new InvalidOperationException("It is required to set the rijndael key identifer."); - } - - AddKeyIdentifierHeader(outgoingContext); - - using (var rijndael = new RijndaelManaged()) - { - rijndael.Key = encryptionKey; - rijndael.Mode = CipherMode.CBC; - rijndael.GenerateIV(); - - using (var encryptor = rijndael.CreateEncryptor()) - using (var memoryStream = new MemoryStream()) - using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - using (var writer = new StreamWriter(cryptoStream)) { - writer.Write(value); - writer.Flush(); - cryptoStream.Flush(); - cryptoStream.FlushFinalBlock(); - return new EncryptedValue + using (var memoryStream = new MemoryStream(encrypted)) { - EncryptedBase64Value = Convert.ToBase64String(memoryStream.ToArray()), - Base64Iv = Convert.ToBase64String(rijndael.IV) - }; + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + { + using (var reader = new StreamReader(cryptoStream)) + { + return reader.ReadToEnd(); + } + } + } } } } @@ -180,7 +186,7 @@ static void VerifyExpiredKeys(IList keys) { continue; } - var message = string.Format("The expired key at index {0} has an invalid length of {1} bytes.", index, key.Length); + var message = $"The expired key at index {index} has an invalid length of {key.Length} bytes."; throw new Exception(message); } } @@ -191,7 +197,7 @@ static void VerifyEncryptionKey(byte[] key) { return; } - var message = string.Format("The encryption key has an invalid length of {0} bytes.", key.Length); + var message = $"The encryption key has an invalid length of {key.Length} bytes."; throw new Exception(message); } @@ -199,37 +205,37 @@ static bool IsValidKey(byte[] key) { using (var rijndael = new RijndaelManaged()) { - var bitLength = key.Length * 8; - return rijndael.ValidKeySize(bitLength); - } - } - - protected virtual void AddKeyIdentifierHeader(OutgoingContext outgoingContext) - { - var outgoingHeaders = outgoingContext.OutgoingLogicalMessage.Headers; + var bitLength = key.Length*8; - outgoingHeaders[Headers.RijndaelKeyIdentifier] = encryptionKeyIdentifier; + var maxValidKeyBitLength = rijndael.LegalKeySizes.Max(keyLength => keyLength.MaxSize); + if (bitLength < maxValidKeyBitLength) + { + Log.WarnFormat("Encryption key is {0} bits which is less than the maximum allowed {1} bits. Consider using a {1}-bit encryption key to obtain the maximum cipher strength", bitLength, maxValidKeyBitLength); + } - if (!outgoingHeaders.ContainsKey(Headers.RijndaelKeyIdentifier)) - { - outgoingHeaders.Add(Headers.RijndaelKeyIdentifier, encryptionKeyIdentifier); + return rijndael.ValidKeySize(bitLength); } } - protected virtual bool TryGetKeyIdentifierHeader(out string keyIdentifier, IncomingContext incomingContext) + protected virtual void AddKeyIdentifierHeader(IOutgoingLogicalMessageContext context) { - var headers = incomingContext.IncomingLogicalMessage.Headers; - return headers.TryGetValue(Headers.RijndaelKeyIdentifier, out keyIdentifier); + context.Headers[Headers.RijndaelKeyIdentifier] = encryptionKeyIdentifier; } - public EncryptedValue Encrypt(string value) + protected virtual bool TryGetKeyIdentifierHeader(out string keyIdentifier, IIncomingLogicalMessageContext context) { - throw new NotSupportedException("Please use overload Encrypt(string, OutgoingContext)"); + return context.Headers.TryGetValue(Headers.RijndaelKeyIdentifier, out keyIdentifier); } - public string Decrypt(EncryptedValue encryptedValue) + protected virtual void ConfigureIV(RijndaelManaged rijndael) { - throw new NotSupportedException("Please use overload Decrypt(string, IncomingContext)"); + rijndael.GenerateIV(); } + + readonly string encryptionKeyIdentifier; + IList decryptionKeys; // Required, as we decrypt in the configured order. + byte[] encryptionKey; + IDictionary keys; + static readonly ILog Log = LogManager.GetLogger(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfig.cs b/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfig.cs index c0aea75970a..7da875559e3 100644 --- a/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfig.cs +++ b/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfig.cs @@ -13,14 +13,8 @@ public class RijndaelEncryptionServiceConfig : ConfigurationSection [ConfigurationProperty("Key", IsRequired = true)] public string Key { - get - { - return this["Key"] as string; - } - set - { - this["Key"] = value; - } + get { return this["Key"] as string; } + set { this["Key"] = value; } } /// @@ -29,14 +23,8 @@ public string Key [ConfigurationProperty("KeyIdentifier", IsRequired = false)] public string KeyIdentifier { - get - { - return (string)this["KeyIdentifier"]; - } - set - { - this["KeyIdentifier"] = value; - } + get { return (string) this["KeyIdentifier"]; } + set { this["KeyIdentifier"] = value; } } /// @@ -45,14 +33,8 @@ public string KeyIdentifier [ConfigurationProperty("ExpiredKeys", IsRequired = false)] public RijndaelExpiredKeyCollection ExpiredKeys { - get - { - return this["ExpiredKeys"] as RijndaelExpiredKeyCollection; - } - set - { - this["ExpiredKeys"] = value; - } + get { return this["ExpiredKeys"] as RijndaelExpiredKeyCollection; } + set { this["ExpiredKeys"] = value; } } @@ -62,15 +44,8 @@ public RijndaelExpiredKeyCollection ExpiredKeys [ConfigurationProperty("KeyFormat", IsRequired = false)] public KeyFormat KeyFormat { - get - { - return (KeyFormat)this["KeyFormat"]; - } - set - { - this["KeyFormat"] = value; - } + get { return (KeyFormat) this["KeyFormat"]; } + set { this["KeyFormat"] = value; } } - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfigValidations.cs b/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfigValidations.cs index fac7b4d4860..782a0a4a631 100644 --- a/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfigValidations.cs +++ b/src/NServiceBus.Core/Encryption/RijndaelEncryptionServiceConfigValidations.cs @@ -1,18 +1,21 @@ namespace NServiceBus { using System.Linq; - using NServiceBus.Config; + using Config; - internal static class RijndaelEncryptionServiceConfigValidations + static class RijndaelEncryptionServiceConfigValidations { public static bool ConfigurationHasDuplicateKeyIdentifiers(RijndaelEncryptionServiceConfig section) { - // Combine all key identifier values, filter the empty ones, split them + // Combine all key identifier values, filter the empty ones, split them return section .ExpiredKeys .Cast() .Select(x => x.KeyIdentifier) - .Union(new[] { section.KeyIdentifier }) + .Union(new[] + { + section.KeyIdentifier + }) .Where(x => !string.IsNullOrEmpty(x)) .Select(x => x.Split(';')) .SelectMany(x => x) diff --git a/src/NServiceBus.Core/Encryption/RijndaelExpiredKey.cs b/src/NServiceBus.Core/Encryption/RijndaelExpiredKey.cs index 3830cc2b839..8edec56ba80 100644 --- a/src/NServiceBus.Core/Encryption/RijndaelExpiredKey.cs +++ b/src/NServiceBus.Core/Encryption/RijndaelExpiredKey.cs @@ -7,21 +7,14 @@ namespace NServiceBus.Config /// public class RijndaelExpiredKey : ConfigurationElement { - /// /// The keys value. /// [ConfigurationProperty("Key", IsRequired = true)] public string Key { - get - { - return (string)this["Key"]; - } - set - { - this["Key"] = value; - } + get { return (string) this["Key"]; } + set { this["Key"] = value; } } @@ -29,15 +22,10 @@ public string Key /// Identifies this key for it to be used for decryption. /// [ConfigurationProperty("KeyIdentifier", IsRequired = false)] - public string KeyIdentifier { - get - { - return (string) this["KeyIdentifier"]; - } - set - { - this["KeyIdentifier"] = value; - } + public string KeyIdentifier + { + get { return (string) this["KeyIdentifier"]; } + set { this["KeyIdentifier"] = value; } } @@ -47,15 +35,8 @@ public string KeyIdentifier { [ConfigurationProperty("KeyFormat", IsRequired = false)] public KeyFormat KeyFormat { - get - { - return (KeyFormat)this["KeyFormat"]; - } - set - { - this["KeyFormat"] = value; - } + get { return (KeyFormat) this["KeyFormat"]; } + set { this["KeyFormat"] = value; } } - } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/RijndaelExpiredKeyCollection.cs b/src/NServiceBus.Core/Encryption/RijndaelExpiredKeyCollection.cs index 932b706732f..53b4b0ba141 100644 --- a/src/NServiceBus.Core/Encryption/RijndaelExpiredKeyCollection.cs +++ b/src/NServiceBus.Core/Encryption/RijndaelExpiredKeyCollection.cs @@ -1,26 +1,40 @@ namespace NServiceBus.Config { - using System; using System.Configuration; /// - /// A configuration element collection of s. + /// A configuration element collection of s. /// public class RijndaelExpiredKeyCollection : ConfigurationElementCollection { /// /// Returns AddRemoveClearMap. /// - public override ConfigurationElementCollectionType CollectionType + public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.AddRemoveClearMap; + + /// + /// Gets/sets the at the given index. + /// + public RijndaelExpiredKey this[int index] { - get + get { return (RijndaelExpiredKey) BaseGet(index); } + set { - return ConfigurationElementCollectionType.AddRemoveClearMap; + if (BaseGet(index) != null) + { + BaseRemoveAt(index); + } + BaseAdd(index, value); } } /// - /// Creates a new . + /// Gets the for the given key. + /// + new public RijndaelExpiredKey this[string key] => (RijndaelExpiredKey) BaseGet(key); + + /// + /// Creates a new . /// protected override ConfigurationElement CreateNewElement() { @@ -28,7 +42,8 @@ protected override ConfigurationElement CreateNewElement() } /// - /// Creates a new , setting its property to the given value. + /// Creates a new , setting its property to the + /// given value. /// protected override ConfigurationElement CreateNewElement(string elementName) { @@ -39,47 +54,17 @@ protected override ConfigurationElement CreateNewElement(string elementName) } /// - /// Returns the Messages property of the given element. + /// Returns the Messages property of the given element. /// - protected override Object GetElementKey(ConfigurationElement element) + protected override object GetElementKey(ConfigurationElement element) { - var encryptionKey = (RijndaelExpiredKey)element; + var encryptionKey = (RijndaelExpiredKey) element; return encryptionKey.Key; } /// - /// Gets/sets the at the given index. - /// - public RijndaelExpiredKey this[int index] - { - get - { - return (RijndaelExpiredKey)BaseGet(index); - } - set - { - if (BaseGet(index) != null) - { - BaseRemoveAt(index); - } - BaseAdd(index, value); - } - } - - /// - /// Gets the for the given key. - /// - new public RijndaelExpiredKey this[string key] - { - get - { - return (RijndaelExpiredKey)BaseGet(key); - } - } - - /// - /// Calls BaseIndexOf on the given . + /// Calls BaseIndexOf on the given . /// public int IndexOf(RijndaelExpiredKey encryptionKey) { @@ -138,11 +123,11 @@ public void Clear() } /// - /// True if the collection is readonly + /// True if the collection is readonly. /// public override bool IsReadOnly() { return false; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/StringConversions.cs b/src/NServiceBus.Core/Encryption/StringConversions.cs new file mode 100644 index 00000000000..4bc5da50344 --- /dev/null +++ b/src/NServiceBus.Core/Encryption/StringConversions.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using Pipeline; + + static class StringConversions + { + public static void EncryptValue(this IEncryptionService encryptionService, ref string stringToEncrypt, IOutgoingLogicalMessageContext context) + { + var encryptedValue = encryptionService.Encrypt(stringToEncrypt, context); + + stringToEncrypt = $"{encryptedValue.EncryptedBase64Value}@{encryptedValue.Base64Iv}"; + } + + public static void DecryptValue(this IEncryptionService encryptionService, ref string stringToDecrypt, IIncomingLogicalMessageContext context) + { + var parts = stringToDecrypt.Split(splitChars, StringSplitOptions.None); + + stringToDecrypt = encryptionService.Decrypt( + new EncryptedValue + { + EncryptedBase64Value = parts[0], + Base64Iv = parts[1] + }, + context + ); + } + + static char[] splitChars = { '@' }; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/WireEncryptedString.cs b/src/NServiceBus.Core/Encryption/WireEncryptedString.cs index 2db7aff47c0..571443a3c7d 100644 --- a/src/NServiceBus.Core/Encryption/WireEncryptedString.cs +++ b/src/NServiceBus.Core/Encryption/WireEncryptedString.cs @@ -10,92 +10,83 @@ public class WireEncryptedString : ISerializable { /// - /// Default constructor + /// Initializes a new instance of . /// public WireEncryptedString() - {} + { + } /// - /// Deserializing constructor + /// Initializes a new instance of . /// public WireEncryptedString(SerializationInfo info, StreamingContext context) { - EncryptedValue = info.GetValue("EncryptedValue", typeof (EncryptedValue)) as EncryptedValue; + Guard.AgainstNull(nameof(info), info); + EncryptedValue = info.GetValue("EncryptedValue", typeof(EncryptedValue)) as EncryptedValue; } + /// /// The unencrypted string. /// public string Value { get; set; } /// - /// The encrypted value of this string + /// The encrypted value of this string. /// public EncryptedValue EncryptedValue { - get - { - if (encryptedValue != null) - return encryptedValue; - - if(EncryptedBase64Value != null) - return new EncryptedValue - { - EncryptedBase64Value = EncryptedBase64Value, - Base64Iv = Base64Iv - }; - return null; - } - set - { - encryptedValue = value; - - if(encryptedValue != null) - { - EncryptedBase64Value = encryptedValue.EncryptedBase64Value; - Base64Iv = encryptedValue.Base64Iv; - } - } + get { return encryptedValue; } + set { encryptedValue = value; } } - EncryptedValue encryptedValue; - - //**** we need to duplicate to make versions > 3.2.7 backwards compatible with 2.X + + // we need to duplicate to make versions > 3.2.7 backwards compatible with 2.X /// - /// Only kept for backwards compatibility reasons + /// Only kept for backwards compatibility reasons. /// - [ObsoleteEx(TreatAsErrorFromVersion = "6.0",RemoveInVersion = "6.0",Message = "No longer required")] + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + Message = "No longer required")] public string EncryptedBase64Value { get; set; } /// - /// Only kept for backwards compatibility reasons + /// Only kept for backwards compatibility reasons. /// - [ObsoleteEx(TreatAsErrorFromVersion = "6.0", RemoveInVersion = "6.0", Message = "No longer required")] + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + Message = "No longer required")] public string Base64Iv { get; set; } - - //**** /// - /// Gets the string value from the WireEncryptedString. + /// Method for making default XML serialization work properly for this type. /// - public static implicit operator string(WireEncryptedString s) + public void GetObjectData(SerializationInfo info, StreamingContext context) { - return s == null ? null : s.Value; + Guard.AgainstNull(nameof(info), info); + info.AddValue("EncryptedValue", EncryptedValue); } /// - /// Creates a new WireEncryptedString from the given string. + /// Gets the string value from the WireEncryptedString. /// - public static implicit operator WireEncryptedString(string s) + public static implicit operator string(WireEncryptedString s) { - return new WireEncryptedString { Value = s }; + return s?.Value; } /// - /// Method for making default XML serialization work properly for this type. + /// Creates a new WireEncryptedString from the given string. /// - public void GetObjectData(SerializationInfo info, StreamingContext context) + public static implicit operator WireEncryptedString(string s) { - info.AddValue("EncryptedValue", EncryptedValue); + return new WireEncryptedString + { + Value = s + }; } + + EncryptedValue encryptedValue; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Encryption/WireEncryptedStringConversions.cs b/src/NServiceBus.Core/Encryption/WireEncryptedStringConversions.cs new file mode 100644 index 00000000000..fca38e12fa6 --- /dev/null +++ b/src/NServiceBus.Core/Encryption/WireEncryptedStringConversions.cs @@ -0,0 +1,24 @@ +namespace NServiceBus +{ + using System; + using Pipeline; + + static class WireEncryptedStringConversions + { + public static void EncryptValue(this IEncryptionService encryptionService, WireEncryptedString wireEncryptedString, IOutgoingLogicalMessageContext context) + { + wireEncryptedString.EncryptedValue = encryptionService.Encrypt(wireEncryptedString.Value, context); + wireEncryptedString.Value = null; + } + + public static void DecryptValue(this IEncryptionService encryptionService, WireEncryptedString wireEncryptedString, IIncomingLogicalMessageContext context) + { + if (wireEncryptedString.EncryptedValue == null) + { + throw new Exception("Encrypted property is missing encryption data"); + } + + wireEncryptedString.Value = encryptionService.Decrypt(wireEncryptedString.EncryptedValue, context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Endpoint.cs b/src/NServiceBus.Core/Endpoint.cs new file mode 100644 index 00000000000..457c9d63bac --- /dev/null +++ b/src/NServiceBus.Core/Endpoint.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + + /// + /// Provides factory methods for creating and starting endpoint instances. + /// + public static class Endpoint + { + /// + /// Creates a new startable endpoint based on the provided configuration. + /// + /// Configuration. + public static Task Create(EndpointConfiguration configuration) + { + Guard.AgainstNull(nameof(configuration), configuration); + var initializable = configuration.Build(); + return initializable.Initialize(); + } + + /// + /// Creates and starts a new endpoint based on the provided configuration. + /// + /// Configuration. + public static async Task Start(EndpointConfiguration configuration) + { + var initializable = await Create(configuration).ConfigureAwait(false); + return await initializable.Start().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/EndpointConfiguration.cs b/src/NServiceBus.Core/EndpointConfiguration.cs new file mode 100644 index 00000000000..6746359d9bd --- /dev/null +++ b/src/NServiceBus.Core/EndpointConfiguration.cs @@ -0,0 +1,287 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Transactions; + using System.Web; + using Config.ConfigurationSource; + using Configuration.AdvanceExtensibility; + using Container; + using Hosting.Helpers; + using ObjectBuilder; + using ObjectBuilder.Common; + using Pipeline; + using Settings; + using Transport; + using Unicast.Messages; + + /// + /// Configuration used to create an endpoint instance. + /// + public partial class EndpointConfiguration : ExposeSettings + { + /// + /// Initializes the endpoint configuration builder. + /// + /// The name of the endpoint being configured. + public EndpointConfiguration(string endpointName) + : base(new SettingsHolder()) + { + ValidateEndpointName(endpointName); + + Settings.Set("NServiceBus.Routing.EndpointName", endpointName); + + Settings.SetDefault(new DefaultConfigurationSource()); + + pipelineCollection = new PipelineConfiguration(); + Settings.Set(pipelineCollection); + Settings.Set(new SatelliteDefinitions()); + + Pipeline = new PipelineSettings(pipelineCollection.Modifications); + + Settings.Set(new QueueBindings()); + + Settings.SetDefault("Endpoint.SendOnly", false); + Settings.SetDefault("Transactions.IsolationLevel", IsolationLevel.ReadCommitted); + Settings.SetDefault("Transactions.DefaultTimeout", TransactionManager.DefaultTimeout); + + Notifications = new Notifications(); + Settings.Set(Notifications); + Settings.Set(new NotificationSubscriptions()); + + conventionsBuilder = new ConventionsBuilder(Settings); + } + + /// + /// Access to the current endpoint . + /// + public Notifications Notifications { get; } + + /// + /// Access to the pipeline configuration. + /// + public PipelineSettings Pipeline { get; } + + /// + /// Used to configure components in the container. + /// + public void RegisterComponents(Action registration) + { + Guard.AgainstNull(nameof(registration), registration); + registrations.Add(registration); + } + + /// + /// Append a list of s to the ignored list. The string is the file name of the assembly. + /// + public void ExcludeAssemblies(params string[] assemblies) + { + Guard.AgainstNull(nameof(assemblies), assemblies); + + if (assemblies.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Passed in a null or empty assembly name.", nameof(assemblies)); + } + excludedAssemblies = excludedAssemblies.Union(assemblies, StringComparer.OrdinalIgnoreCase).ToList(); + } + + /// + /// Append a list of s to the ignored list. + /// + public void ExcludeTypes(params Type[] types) + { + Guard.AgainstNull(nameof(types), types); + if (types.Any(x => x == null)) + { + throw new ArgumentException("Passed in a null or empty type.", nameof(types)); + } + + excludedTypes = excludedTypes.Union(types).ToList(); + } + + /// + /// Specify to scan nested directories when performing assembly scanning. + /// + public void ScanAssembliesInNestedDirectories() + { + scanAssembliesInNestedDirectories = true; + } + + /// + /// Configures the endpoint to be send-only. + /// + public void SendOnly() + { + Settings.Set("Endpoint.SendOnly", true); + } + + /// + /// Overrides the default configuration source. + /// + public void CustomConfigurationSource(IConfigurationSource configurationSource) + { + Guard.AgainstNull(nameof(configurationSource), configurationSource); + Settings.Set(configurationSource); + } + + /// + /// Defines the conventions to use for this endpoint. + /// + public ConventionsBuilder Conventions() + { + return conventionsBuilder; + } + + /// + /// Defines a custom builder to use. + /// + /// The builder type of the . + public void UseContainer(Action customizations = null) where T : ContainerDefinition, new() + { + customizations?.Invoke(new ContainerCustomizations(Settings)); + + UseContainer(typeof(T)); + } + + /// + /// Defines a custom builder to use. + /// + /// The type of the . + public void UseContainer(Type definitionType) + { + Guard.AgainstNull(nameof(definitionType), definitionType); + Guard.TypeHasDefaultConstructor(definitionType, nameof(definitionType)); + + UseContainer(definitionType.Construct().CreateContainer(Settings)); + } + + /// + /// Uses an already active instance of a builder. + /// + /// The instance to use. + public void UseContainer(IContainer builder) + { + Guard.AgainstNull(nameof(builder), builder); + customBuilder = builder; + } + + /// + /// Specifies the range of types that NServiceBus scans for handlers etc. + /// + internal void TypesToScanInternal(IEnumerable typesToScan) + { + scannedTypes = typesToScan.ToList(); + } + + /// + /// Creates the configuration object. + /// + internal InitializableEndpoint Build() + { + if (scannedTypes == null) + { + var directoryToScan = AppDomain.CurrentDomain.BaseDirectory; + if (HttpRuntime.AppDomainAppId != null) + { + directoryToScan = HttpRuntime.BinDirectory; + } + + scannedTypes = GetAllowedTypes(directoryToScan); + } + else + { + scannedTypes = scannedTypes.Union(GetAllowedCoreTypes()).ToList(); + } + + Settings.SetDefault("TypesToScan", scannedTypes); + ActivateAndInvoke(scannedTypes, t => t.Customize(this)); + + UseTransportExtensions.EnsureTransportConfigured(this); + var container = customBuilder ?? new AutofacObjectBuilder(); + + var conventions = conventionsBuilder.Conventions; + Settings.SetDefault(conventions); + var messageMetadataRegistry = new MessageMetadataRegistry(conventions); + messageMetadataRegistry.RegisterMessageTypesFoundIn(Settings.GetAvailableTypes()); + + Settings.SetDefault(messageMetadataRegistry); + + return new InitializableEndpoint(Settings, container, registrations, Pipeline, pipelineCollection); + } + + static void ValidateEndpointName(string endpointName) + { + if (string.IsNullOrWhiteSpace(endpointName)) + { + throw new ArgumentException("Endpoint name must not be empty", nameof(endpointName)); + } + + if (endpointName.Contains("@")) + { + throw new ArgumentException("Endpoint name must not contain an '@' character.", nameof(endpointName)); + } + } + + static void ForAllTypes(IEnumerable types, Action action) where T : class + { + // ReSharper disable HeapView.SlowDelegateCreation + foreach (var type in types.Where(t => typeof(T).IsAssignableFrom(t) && !(t.IsAbstract || t.IsInterface))) + { + action(type); + } + // ReSharper restore HeapView.SlowDelegateCreation + } + + static void ActivateAndInvoke(IList types, Action action) where T : class + { + ForAllTypes(types, t => + { + if (!HasDefaultConstructor(t)) + { + throw new Exception($"Unable to create the type '{t.Name}'. Types implementing '{typeof(T).Name}' must have a public parameterless (default) constructor."); + } + + var instanceToInvoke = (T) Activator.CreateInstance(t); + action(instanceToInvoke); + }); + } + + static bool HasDefaultConstructor(Type type) => type.GetConstructor(Type.EmptyTypes) != null; + + List GetAllowedTypes(string path) + { + var assemblyScanner = new AssemblyScanner(path) + { + AssembliesToSkip = excludedAssemblies, + TypesToSkip = excludedTypes, + ScanNestedDirectories = scanAssembliesInNestedDirectories + }; + return assemblyScanner + .GetScannableAssemblies() + .Types; + } + + List GetAllowedCoreTypes() + { + var assemblyScanner = new AssemblyScanner(Assembly.GetExecutingAssembly()) + { + TypesToSkip = excludedTypes, + ScanNestedDirectories = scanAssembliesInNestedDirectories + }; + return assemblyScanner + .GetScannableAssemblies() + .Types; + } + + ConventionsBuilder conventionsBuilder; + IContainer customBuilder; + List excludedAssemblies = new List(); + List excludedTypes = new List(); + PipelineConfiguration pipelineCollection; + List> registrations = new List>(); + bool scanAssembliesInNestedDirectories; + List scannedTypes; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/EnumerableEx.cs b/src/NServiceBus.Core/EnumerableEx.cs new file mode 100644 index 00000000000..0737c4af034 --- /dev/null +++ b/src/NServiceBus.Core/EnumerableEx.cs @@ -0,0 +1,84 @@ +namespace NServiceBus +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Janitor; + + static class EnumerableEx + { + public static IEnumerable EnsureNonEmpty(this IEnumerable source, Func exceptionMessage) + { + return new NonEmptyEnumerable(source, () => { throw new Exception(exceptionMessage()); }); + } + + public static IEnumerable EnsureNonEmpty(this IEnumerable source, Func emptyElement) + { + return new NonEmptyEnumerable(source, emptyElement); + } + + class NonEmptyEnumerable : IEnumerable + { + public NonEmptyEnumerable(IEnumerable source, Func ifEmptyCallback) + { + this.source = source; + this.ifEmptyCallback = ifEmptyCallback; + } + + public IEnumerator GetEnumerator() + { + return new NonEmptyEnumerator(source.GetEnumerator(), ifEmptyCallback); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + Func ifEmptyCallback; + IEnumerable source; + + [SkipWeaving] + class NonEmptyEnumerator : IEnumerator + { + public NonEmptyEnumerator(IEnumerator source, Func ifEmptyCallback) + { + this.source = source; + this.ifEmptyCallback = ifEmptyCallback; + } + + public void Dispose() + { + source.Dispose(); + } + + public bool MoveNext() + { + var result = source.MoveNext(); + if (!result && !enumerationStarted) + { + isEmpty = true; + emptyValue = ifEmptyCallback(); + result = true; + } + enumerationStarted = true; + return result; + } + + public void Reset() + { + source.Reset(); + } + + public T Current => isEmpty ? emptyValue : source.Current; + + object IEnumerator.Current => Current; + T emptyValue; + bool enumerationStarted; + Func ifEmptyCallback; + bool isEmpty; + IEnumerator source; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ExceptionExtensions.cs b/src/NServiceBus.Core/ExceptionExtensions.cs index 45a865c631d..0fb3813f59d 100644 --- a/src/NServiceBus.Core/ExceptionExtensions.cs +++ b/src/NServiceBus.Core/ExceptionExtensions.cs @@ -12,7 +12,7 @@ public static string GetMessage(this Exception exception) } catch (Exception) { - return string.Format("Could not read Message from exception type '{0}'.", exception.GetType()); + return $"Could not read Message from exception type '{exception.GetType()}'."; } } } diff --git a/src/NServiceBus.Core/Extensibility/ContextBag.cs b/src/NServiceBus.Core/Extensibility/ContextBag.cs new file mode 100644 index 00000000000..1b80619cb75 --- /dev/null +++ b/src/NServiceBus.Core/Extensibility/ContextBag.cs @@ -0,0 +1,153 @@ +namespace NServiceBus.Extensibility +{ + using System.Collections.Generic; + + /// + /// A string object bag of context objects. + /// + public class ContextBag : ReadOnlyContextBag + { + /// + /// Initialized a new instance of . + /// + public ContextBag(ContextBag parentBag = null) + { + this.parentBag = parentBag; + } + + /// + /// Retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The type instance. + public T Get() + { + return Get(typeof(T).FullName); + } + + /// + /// Tries to retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The type instance. + /// true if found, otherwise false. + public bool TryGet(out T result) + { + return TryGet(typeof(T).FullName, out result); + } + + /// + /// Tries to retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The key of the value being looked up. + /// The type instance. + /// true if found, otherwise false. + public bool TryGet(string key, out T result) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + object value; + if (stash.TryGetValue(key, out value)) + { + result = (T) value; + return true; + } + + if (parentBag != null) + { + return parentBag.TryGet(key, out result); + } + + result = default(T); + return false; + } + + /// + /// Gets the requested extension, a new one will be created if needed. + /// + public T GetOrCreate() where T : class, new() + { + T value; + + if (TryGet(out value)) + { + return value; + } + + var newInstance = new T(); + + Set(newInstance); + + return newInstance; + } + + + /// + /// Stores the type instance in the context. + /// + /// The type to store. + /// The instance type to store. + public void Set(T t) + { + Set(typeof(T).FullName, t); + } + + + /// + /// Removes the instance type from the context. + /// + /// The type to remove. + public void Remove() + { + Remove(typeof(T).FullName); + } + + /// + /// Removes the instance type from the context. + /// + /// The key of the value being removed. + public void Remove(string key) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + stash.Remove(key); + } + + /// + /// Stores the passed instance in the context. + /// + public void Set(string key, T t) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + stash[key] = t; + } + + /// + /// Merges the passed context into this one. + /// + /// The source context. + internal void Merge(ContextBag context) + { + foreach (var kvp in context.stash) + { + stash[kvp.Key] = kvp.Value; + } + } + + T Get(string key) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + T result; + + if (!TryGet(key, out result)) + { + throw new KeyNotFoundException("No item found in behavior context with key: " + key); + } + + return result; + } + + ContextBag parentBag; + + Dictionary stash = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Extensibility/ExtendableOptions.cs b/src/NServiceBus.Core/Extensibility/ExtendableOptions.cs new file mode 100644 index 00000000000..663717cace0 --- /dev/null +++ b/src/NServiceBus.Core/Extensibility/ExtendableOptions.cs @@ -0,0 +1,30 @@ +namespace NServiceBus.Extensibility +{ + using System.Collections.Generic; + + /// + /// Provide a base class for extendable options. + /// + public abstract class ExtendableOptions + { + /// + /// Creates an instance of an extendable option. + /// + protected ExtendableOptions() + { + Context = new ContextBag(); + OutgoingHeaders = new Dictionary(); + MessageId = CombGuid.Generate().ToString(); + } + + internal ContextBag Context { get; } + + internal string MessageId + { + get { return OutgoingHeaders[Headers.MessageId]; } + set { OutgoingHeaders[Headers.MessageId] = value; } + } + + internal Dictionary OutgoingHeaders { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Extensibility/ExtendableOptionsExtensions.cs b/src/NServiceBus.Core/Extensibility/ExtendableOptionsExtensions.cs new file mode 100644 index 00000000000..7ac170e5270 --- /dev/null +++ b/src/NServiceBus.Core/Extensibility/ExtendableOptionsExtensions.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Extensibility +{ + /// + /// Provides hidden access to the extension context. + /// + public static class ExtendableOptionsExtensions + { + /// + /// Gets access to a "bucket", this allows the developer to pass information from extension methods down to behaviors. + /// + /// Extendable options instance. + /// A big bucket. + public static ContextBag GetExtensions(this ExtendableOptions options) + { + return options.Context; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Extensibility/IExtendable.cs b/src/NServiceBus.Core/Extensibility/IExtendable.cs new file mode 100644 index 00000000000..dceed005be2 --- /dev/null +++ b/src/NServiceBus.Core/Extensibility/IExtendable.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Extensibility +{ + using System.ComponentModel; + + /// + /// Marks a class as extendable by giving access to a . + /// + public interface IExtendable + { + /// + /// A which can be used to extend the current object. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + ContextBag Extensions { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Extensibility/ReadonlyContextBag.cs b/src/NServiceBus.Core/Extensibility/ReadonlyContextBag.cs new file mode 100644 index 00000000000..f9bd4d8c009 --- /dev/null +++ b/src/NServiceBus.Core/Extensibility/ReadonlyContextBag.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Extensibility +{ + /// + /// Context bag which is readonly. + /// + public interface ReadOnlyContextBag + { + /// + /// Retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The type instance. + T Get(); + + /// + /// Tries to retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The type instance. + /// true if found, otherwise false. + bool TryGet(out T result); + + /// + /// Tries to retrieves the specified type from the context. + /// + /// The type to retrieve. + /// The key of the value being looked up. + /// The type instance. + /// true if found, otherwise false. + bool TryGet(string key, out T result); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ExtensionMethods.cs b/src/NServiceBus.Core/ExtensionMethods.cs deleted file mode 100644 index e41b55fd4a3..00000000000 --- a/src/NServiceBus.Core/ExtensionMethods.cs +++ /dev/null @@ -1,90 +0,0 @@ -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - - /// - /// Class containing extension methods for base class libraries for using interface-based messages. - /// - public static class ExtensionMethods - { - /// - /// Get the header with the given key. Cannot be used to change its value. - /// - /// The . - /// The message to retrieve a header from. - /// The header key. - /// The value assigned to the header. - public static string GetMessageHeader(this IBus bus, object msg, string key) - { - var manageMessageHeaders = bus as IManageMessageHeaders; - if (manageMessageHeaders != null) - { - return manageMessageHeaders.GetHeaderAction(msg, key); - } - - throw new InvalidOperationException("bus does not implement IManageMessageHeaders"); - } - - /// - /// Sets the value of the header for the given key. - /// - /// The . - /// The message to add a header to. - /// The header key. - /// The value to assign to the header. - public static void SetMessageHeader(this ISendOnlyBus bus, object msg, string key, string value) - { - var manageMessageHeaders = bus as IManageMessageHeaders; - if (manageMessageHeaders != null) - { - manageMessageHeaders.SetHeaderAction(msg, key, value); - return; - } - - throw new InvalidOperationException("bus does not implement IManageMessageHeaders"); - } - - /// - /// Get the header with the given key. Cannot be used to change its value. - /// - /// The to retrieve a header from. - /// The header key. - /// The value assigned to the header. - [ObsoleteEx( - Replacement = "bus.GetMessageHeader(msg, key)", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static string GetHeader(this IMessage msg, string key) - { - throw new InvalidOperationException(); - } - - /// - /// Sets the value of the header for the given key. - /// - /// The to add a header to. - /// The header key. - /// The value to assign to the header. - [ObsoleteEx( - Replacement = "bus.SetMessageHeader(msg, key, value)", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static void SetHeader(this IMessage msg, string key, string value) - { - throw new InvalidOperationException(); - } - - /// - /// The object used to see whether headers requested are for the handled message. - /// - public static object CurrentMessageBeingHandled { get { return currentMessageBeingHandled; } set { currentMessageBeingHandled = value; } } - - [ThreadStatic] - static object currentMessageBeingHandled; - - - - } -} diff --git a/src/NServiceBus.Core/Faults/ErrorQueueSettings.cs b/src/NServiceBus.Core/Faults/ErrorQueueSettings.cs deleted file mode 100644 index 527f5eea9db..00000000000 --- a/src/NServiceBus.Core/Faults/ErrorQueueSettings.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.Faults -{ - using System.Configuration; - using NServiceBus.Config; - using NServiceBus.Logging; - using NServiceBus.Settings; - using NServiceBus.Utils; - - static class ErrorQueueSettings - { - public static Address GetConfiguredErrorQueue(ReadOnlySettings settings) - { - var errorQueue = Address.Undefined; - - var section = settings.GetConfigSection(); - if (section != null) - { - if (string.IsNullOrWhiteSpace(section.ErrorQueue)) - { - throw new ConfigurationErrorsException( - "'MessageForwardingInCaseOfFaultConfig' configuration section is found but 'ErrorQueue' value is missing." + - "\n The following is an example for adding such a value to your app config: " + - "\n \n"); - } - - Logger.Debug("Error queue retrieved from element in config file."); - - errorQueue = Address.Parse(section.ErrorQueue); - } - else - { - var registryErrorQueue = RegistryReader.Read("ErrorQueue"); - if (!string.IsNullOrWhiteSpace(registryErrorQueue)) - { - Logger.Debug("Error queue retrieved from registry settings."); - errorQueue = Address.Parse(registryErrorQueue); - } - } - - if (errorQueue == Address.Undefined) - { - throw new ConfigurationErrorsException("Faults forwarding requires an error queue to be specified. Please add a 'MessageForwardingInCaseOfFaultConfig' section to your app.config" + - "\n or configure a global one using the powershell command: Set-NServiceBusLocalMachineSettings -ErrorQueue {address of error queue}"); - } - - return errorQueue; - - } - - static ILog Logger = LogManager.GetLogger(typeof(ErrorQueueSettings)); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/ErrorSubscribers.cs b/src/NServiceBus.Core/Faults/ErrorSubscribers.cs deleted file mode 100644 index 55888a32eb4..00000000000 --- a/src/NServiceBus.Core/Faults/ErrorSubscribers.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.Features -{ - class ErrorSubscribers : Feature - { - public ErrorSubscribers() - { - EnableByDefault(); - } - - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } -} diff --git a/src/NServiceBus.Core/Faults/ErrorsNotifications.cs b/src/NServiceBus.Core/Faults/ErrorsNotifications.cs deleted file mode 100644 index ed3360552e0..00000000000 --- a/src/NServiceBus.Core/Faults/ErrorsNotifications.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace NServiceBus.Faults -{ - using System; - using System.Collections.Generic; - - /// - /// Errors notifications - /// - public class ErrorsNotifications : IDisposable - { - /// - /// Notification when a message is moved to the error queue. - /// - public IObservable MessageSentToErrorQueue - { - get { return erroneousMessageList; } - } - - /// - /// Notification when a message fails a first level retry. - /// - public IObservable MessageHasFailedAFirstLevelRetryAttempt - { - get { return firstLevelRetryList; } - } - - /// - /// Notification when a message is sent to second level retires queue. - /// - public IObservable MessageHasBeenSentToSecondLevelRetries - { - get { return secondLevelRetryList; } - } - - void IDisposable.Dispose() - { - // Injected - } - - internal void InvokeMessageHasBeenSentToErrorQueue(TransportMessage message, Exception exception) - { - erroneousMessageList.OnNext(new FailedMessage(new Dictionary(message.Headers), CopyOfBody(message.Body), exception)); - } - - internal void InvokeMessageHasFailedAFirstLevelRetryAttempt(int firstLevelRetryAttempt, TransportMessage message, Exception exception) - { - firstLevelRetryList.OnNext(new FirstLevelRetry(new Dictionary(message.Headers), CopyOfBody(message.Body), exception, firstLevelRetryAttempt)); - } - - internal void InvokeMessageHasBeenSentToSecondLevelRetries(int secondLevelRetryAttempt, TransportMessage message, Exception exception) - { - secondLevelRetryList.OnNext(new SecondLevelRetry(new Dictionary(message.Headers), CopyOfBody(message.Body), exception, secondLevelRetryAttempt)); - } - - static byte[] CopyOfBody(byte[] body) - { - if (body == null) - { - return null; - } - - var copyBody = new byte[body.Length]; - - Buffer.BlockCopy(body, 0, copyBody, 0, body.Length); - - return copyBody; - } - - Observable erroneousMessageList = new Observable(); - Observable firstLevelRetryList = new Observable(); - Observable secondLevelRetryList = new Observable(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/FailedMessage.cs b/src/NServiceBus.Core/Faults/FailedMessage.cs deleted file mode 100644 index 2aae98f3590..00000000000 --- a/src/NServiceBus.Core/Faults/FailedMessage.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NServiceBus.Faults -{ - using System; - using System.Collections.Generic; - - /// - /// Error message event data. - /// - public struct FailedMessage - { - readonly Dictionary headers; - readonly byte[] body; - readonly Exception exception; - - /// - /// Creates a new instance of . - /// - /// Message headers. - /// Message body. - /// Exception thrown. - public FailedMessage(Dictionary headers, byte[] body, Exception exception) - { - this.headers = headers; - this.body = body; - this.exception = exception; - } - - /// - /// Gets the message headers. - /// - public Dictionary Headers { get { return headers; }} - - /// - /// Gets a byte array to the body content of the message - /// - public byte[] Body { get { return body; } } - - /// - /// The exception that caused this message to fail. - /// - public Exception Exception { get { return exception; } } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/FirstLevelRetry.cs b/src/NServiceBus.Core/Faults/FirstLevelRetry.cs deleted file mode 100644 index 71e3fcc5be1..00000000000 --- a/src/NServiceBus.Core/Faults/FirstLevelRetry.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus.Faults -{ - using System; - using System.Collections.Generic; - - /// - /// first level retry event data. - /// - public struct FirstLevelRetry - { - readonly Dictionary headers; - readonly byte[] body; - readonly Exception exception; - readonly int retryAttempt; - - /// - /// Creates a new instance of . - /// - /// Message headers. - /// Message body. - /// Exception thrown. - /// Number of retry attempt - public FirstLevelRetry(Dictionary headers, byte[] body, Exception exception, int retryAttempt) - { - this.headers = headers; - this.body = body; - this.exception = exception; - this.retryAttempt = retryAttempt; - } - - /// - /// Gets the message headers. - /// - public Dictionary Headers { get { return headers; } } - - /// - /// Gets a byte array to the body content of the message - /// - public byte[] Body { get { return body; } } - - /// - /// The exception that caused this message to fail. - /// - public Exception Exception { get { return exception; } } - - /// - /// Number of retry attempt. - /// - public int RetryAttempt { get { return retryAttempt; } } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/Forwarder/Config/FaultsQueueCreator.cs b/src/NServiceBus.Core/Faults/Forwarder/Config/FaultsQueueCreator.cs deleted file mode 100644 index 8719dc641cf..00000000000 --- a/src/NServiceBus.Core/Faults/Forwarder/Config/FaultsQueueCreator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NServiceBus.Faults.Forwarder.Config -{ - using Unicast.Queuing; - - class FaultsQueueCreator : IWantQueueCreated - { - public Address ErrorQueue { get; set; } - - public Address Address - { - get { return ErrorQueue; } - } - - public bool Enabled { get; set; } - - public bool ShouldCreateQueue() - { - return Enabled; - } - } -} diff --git a/src/NServiceBus.Core/Faults/Forwarder/FaultManager.cs b/src/NServiceBus.Core/Faults/Forwarder/FaultManager.cs deleted file mode 100644 index 5cf537f7b57..00000000000 --- a/src/NServiceBus.Core/Faults/Forwarder/FaultManager.cs +++ /dev/null @@ -1,174 +0,0 @@ -namespace NServiceBus.Faults.Forwarder -{ - using System; - using NServiceBus.Logging; - using NServiceBus.SecondLevelRetries; - using NServiceBus.SecondLevelRetries.Helpers; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NServiceBus.Unicast.Queuing; - - /// - /// Implementation of IManageMessageFailures by forwarding messages - /// using ISendMessages. - /// - class FaultManager : IManageMessageFailures - { - public FaultManager(ISendMessages sender, Configure config, BusNotifications busNotifications) - { - this.sender = sender; - this.config = config; - this.busNotifications = busNotifications; - } - - /// - /// Endpoint to which message failures are forwarded - /// - public Address ErrorQueue { get; set; } - - /// - /// The address of the Second Level Retries input queue when SLR is enabled - /// - public Address RetriesQueue { get; set; } - - public SecondLevelRetriesConfiguration SecondLevelRetriesConfiguration { get; set; } - - void IManageMessageFailures.SerializationFailedForMessage(TransportMessage message, Exception e) - { - TryHandleFailure(() => HandleSerializationFailedForMessage(message, e)); - } - - void IManageMessageFailures.ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - TryHandleFailure(() => HandleProcessingAlwaysFailsForMessage(message, e, GetNumberOfFirstLevelRetries(message))); - } - - void IManageMessageFailures.Init(Address address) - { - localAddress = address; - } - - void HandleSerializationFailedForMessage(TransportMessage message, Exception e) - { - message.SetExceptionHeaders(e, localAddress ?? config.LocalAddress); - SendToErrorQueue(message, e); - } - - void HandleProcessingAlwaysFailsForMessage(TransportMessage message, Exception e, int numberOfRetries) - { - message.SetExceptionHeaders(e, localAddress ?? config.LocalAddress); - - if (MessageWasSentFromSLR(message)) - { - SendToErrorQueue(message, e); - return; - } - - var flrPart = numberOfRetries > 0 - ? $"Message with '{message.Id}' id has failed FLR and" - : $"FLR is disabled and the message '{message.Id}'"; - - //HACK: We need this hack here till we refactor the SLR to be a first class concept in the TransportReceiver - if (RetriesQueue == null) - { - Logger.ErrorFormat("{0} will be moved to the configured error queue.", flrPart); - SendToErrorQueue(message, e); - return; - } - - var defer = SecondLevelRetriesConfiguration.RetryPolicy.Invoke(message); - - if (defer < TimeSpan.Zero) - { - Logger.ErrorFormat( - "SLR has failed to resolve the issue with message {0} and will be forwarded to the error queue at {1}", - message.Id, ErrorQueue); - SendToErrorQueue(message, e); - return; - } - - SendToRetriesQueue(message, e, flrPart); - } - - void SendToErrorQueue(TransportMessage message, Exception exception) - { - message.TimeToBeReceived = TimeSpan.MaxValue; - - if (message.Headers.ContainsKey(Headers.FLRetries)) - { - message.Headers.Remove(Headers.FLRetries); - } - - if (message.Headers.ContainsKey(Headers.Retries)) - { - message.Headers.Remove(Headers.Retries); - } - - sender.Send(message, new SendOptions(ErrorQueue)); - busNotifications.Errors.InvokeMessageHasBeenSentToErrorQueue(message, exception); - } - - void SendToRetriesQueue(TransportMessage message, Exception e, string flrPart) - { - message.TimeToBeReceived = TimeSpan.MaxValue; - sender.Send(message, new SendOptions(RetriesQueue)); - - var retryAttempt = TransportMessageHeaderHelper.GetNumberOfRetries(message) + 1; - - Logger.WarnFormat("{0} will be handed over to SLR for retry attempt {1}.", flrPart, retryAttempt); - busNotifications.Errors.InvokeMessageHasBeenSentToSecondLevelRetries(retryAttempt, message, e); - } - - void TryHandleFailure(Action failureHandlingAction) - { - try - { - failureHandlingAction(); - } - catch (QueueNotFoundException exception) - { - var errorMessage = $"Could not forward failed message to error queue '{exception.Queue}' as it could not be found."; - Logger.Fatal(errorMessage); - throw new InvalidOperationException(errorMessage, exception); - } - catch (Exception exception) - { - var errorMessage = "Could not forward failed message to error queue."; - Logger.Fatal(errorMessage, exception); - throw new InvalidOperationException(errorMessage, exception); - } - } - - bool MessageWasSentFromSLR(TransportMessage message) - { - if (RetriesQueue == null) - { - return false; - } - - // if the reply to address == ErrorQueue and RealErrorQueue is not null, the - // SecondLevelRetries sat is running and the error happened within that sat. - return TransportMessageHeaderHelper.GetAddressOfFaultingEndpoint(message) == RetriesQueue; - } - - static int GetNumberOfFirstLevelRetries(TransportMessage message) - { - string value; - if (message.Headers.TryGetValue(Headers.FLRetries, out value)) - { - int i; - if (int.TryParse(value, out i)) - { - return i; - } - } - return 0; - } - - static ILog Logger = LogManager.GetLogger(); - readonly BusNotifications busNotifications; - readonly Configure config; - readonly ISendMessages sender; - Address localAddress; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/Forwarder/ForwarderFaultManager.cs b/src/NServiceBus.Core/Faults/Forwarder/ForwarderFaultManager.cs deleted file mode 100644 index 9d333dd07d5..00000000000 --- a/src/NServiceBus.Core/Faults/Forwarder/ForwarderFaultManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus.Features -{ - using NServiceBus.Faults; - using NServiceBus.Faults.Forwarder; - using NServiceBus.Faults.Forwarder.Config; - - class ForwarderFaultManager : Feature - { - internal ForwarderFaultManager() - { - EnableByDefault(); - Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"), "Send only endpoints can't be used to forward received messages to the error queue as the endpoint requires receive capabilities"); - Prerequisite(c => !c.Container.HasComponent(), "An IManageMessageFailures implementation is already registered."); - } - - protected internal override void Setup(FeatureConfigurationContext context) - { - if (context.Settings.GetOrDefault("Endpoint.SendOnly")) - { - return; - } - - var errorQueue = ErrorQueueSettings.GetConfiguredErrorQueue(context.Settings); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(fm => fm.ErrorQueue, errorQueue); - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.Enabled, true) - .ConfigureProperty(t => t.ErrorQueue, errorQueue); - } - - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/IManageMessageFailures.cs b/src/NServiceBus.Core/Faults/IManageMessageFailures.cs deleted file mode 100644 index 015e3147994..00000000000 --- a/src/NServiceBus.Core/Faults/IManageMessageFailures.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Faults -{ - using System; - - /// - /// Interface for defining how message failures will be handled. - /// - public interface IManageMessageFailures - { - /// - /// Invoked when the deserialization of a message failed. - /// - void SerializationFailedForMessage(TransportMessage message, Exception e); - - /// - /// Invoked when a message has failed its processing the maximum number of time configured. - /// - void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e); - - /// - /// Initializes the fault manager - /// - /// The address of the message source - void Init(Address address); - } -} diff --git a/src/NServiceBus.Core/Faults/InMemory/FaultManager.cs b/src/NServiceBus.Core/Faults/InMemory/FaultManager.cs deleted file mode 100644 index fb1eb38d4de..00000000000 --- a/src/NServiceBus.Core/Faults/InMemory/FaultManager.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace NServiceBus.Faults.InMemory -{ - using System; - using Logging; - - /// - /// Logging implementation of IManageMessageFailures. - /// - class FaultManager : IManageMessageFailures - { - void IManageMessageFailures.SerializationFailedForMessage(TransportMessage message, Exception e) - { - logger.Error("Serialization failed for message with ID " + message.Id + ".", e); - } - - void IManageMessageFailures.ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - logger.Error("Message processing always fails for message with ID " + message.Id + ".", e); - } - - /// - /// Initializes the fault manager - /// - /// The address of the message source - public void Init(Address address) - { - - } - - static ILog logger = LogManager.GetLogger(); - } -} diff --git a/src/NServiceBus.Core/Faults/InMemory/InMemoryFaultManager.cs b/src/NServiceBus.Core/Faults/InMemory/InMemoryFaultManager.cs deleted file mode 100644 index 0321d277437..00000000000 --- a/src/NServiceBus.Core/Faults/InMemory/InMemoryFaultManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NServiceBus.Features -{ - using NServiceBus.Faults.InMemory; - - class InMemoryFaultManager : Feature - { - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/SecondLevelRetry.cs b/src/NServiceBus.Core/Faults/SecondLevelRetry.cs deleted file mode 100644 index ba06a1d9c6f..00000000000 --- a/src/NServiceBus.Core/Faults/SecondLevelRetry.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus.Faults -{ - using System; - using System.Collections.Generic; - - /// - /// Second level retry event data. - /// - public struct SecondLevelRetry - { - readonly Dictionary headers; - readonly byte[] body; - readonly Exception exception; - readonly int retryAttempt; - - /// - /// Creates a new instance of . - /// - /// Message headers. - /// Message body. - /// Exception thrown. - /// Number of retry attempt - public SecondLevelRetry(Dictionary headers, byte[] body, Exception exception, int retryAttempt) - { - this.headers = headers; - this.body = body; - this.exception = exception; - this.retryAttempt = retryAttempt; - } - - /// - /// Gets the message headers. - /// - public Dictionary Headers { get { return headers; } } - - /// - /// Gets a byte array to the body content of the message - /// - public byte[] Body { get { return body; } } - - /// - /// The exception that caused this message to fail. - /// - public Exception Exception { get { return exception; } } - - /// - /// Number of retry attempt. - /// - public int RetryAttempt { get { return retryAttempt; } } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/AdvanceExtensibilityExtensions.cs b/src/NServiceBus.Core/Features/AdvanceExtensibilityExtensions.cs index b1372618daf..a1e30769e3c 100644 --- a/src/NServiceBus.Core/Features/AdvanceExtensibilityExtensions.cs +++ b/src/NServiceBus.Core/Features/AdvanceExtensibilityExtensions.cs @@ -1,6 +1,6 @@ namespace NServiceBus.Configuration.AdvanceExtensibility { - using NServiceBus.Settings; + using Settings; /// /// Extension methods declarations. @@ -8,10 +8,11 @@ public static class AdvanceExtensibilityExtensions { /// - /// Gives access to the for extensibility. + /// Gives access to the for extensibility. /// public static SettingsHolder GetSettings(this ExposeSettings config) { + Guard.AgainstNull(nameof(config), config); return config.Settings; } } diff --git a/src/NServiceBus.Core/Features/ConfigurationBuilderExtensions.cs b/src/NServiceBus.Core/Features/ConfigurationBuilderExtensions.cs deleted file mode 100644 index 9dc289cc7c9..00000000000 --- a/src/NServiceBus.Core/Features/ConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus -{ - using System; - using Features; - - /// - /// Extension methods declarations. - /// - public static class ConfigurationBuilderExtensions - { - /// - /// Enables the given feature - /// - public static void EnableFeature(this BusConfiguration config) where T : Feature - { - config.EnableFeature(typeof(T)); - } - - /// - /// Enables the given feature - /// - /// - /// The feature to enable - public static void EnableFeature(this BusConfiguration config, Type featureType) - { - config.Settings.Set(featureType.FullName, true); - } - - /// - /// Disables the given feature - /// - public static void DisableFeature(this BusConfiguration config) where T : Feature - { - config.DisableFeature(typeof(T)); - } - - /// - /// Enables the given feature - /// - /// - /// The feature to disable - public static void DisableFeature(this BusConfiguration config, Type featureType) - { - config.Settings.Set(featureType.FullName, false); - } - } -} diff --git a/src/NServiceBus.Core/Features/DisplayDiagnosticsForFeatures.cs b/src/NServiceBus.Core/Features/DisplayDiagnosticsForFeatures.cs index 0371db473b5..031fc8aa67d 100644 --- a/src/NServiceBus.Core/Features/DisplayDiagnosticsForFeatures.cs +++ b/src/NServiceBus.Core/Features/DisplayDiagnosticsForFeatures.cs @@ -1,27 +1,23 @@ namespace NServiceBus.Features { - using System; using System.Linq; using System.Text; - using Config; using Logging; - class DisplayDiagnosticsForFeatures : IWantToRunWhenConfigurationIsComplete + class DisplayDiagnosticsForFeatures { - public FeaturesReport FeaturesReport { get; set; } - - public void Run(Configure config) + public static void Run(FeaturesReport report) { var statusText = new StringBuilder(); statusText.AppendLine("------------- FEATURES ----------------"); - foreach (var diagnosticData in FeaturesReport.Features) + foreach (var diagnosticData in report.Features) { - statusText.AppendLine(string.Format("Name: {0}", diagnosticData.Name)); - statusText.AppendLine(string.Format("Version: {0}", diagnosticData.Version)); - statusText.AppendLine(string.Format("Enabled by Default: {0}", diagnosticData.EnabledByDefault ? "Yes" : "No")); - statusText.AppendLine(string.Format("Status: {0}", diagnosticData.Active ? "Enabled" : "Disabled")); + statusText.AppendLine($"Name: {diagnosticData.Name}"); + statusText.AppendLine($"Version: {diagnosticData.Version}"); + statusText.AppendLine($"Enabled by Default: {(diagnosticData.EnabledByDefault ? "Yes" : "No")}"); + statusText.AppendLine($"Status: {(diagnosticData.Active ? "Enabled" : "Disabled")}"); if (!diagnosticData.Active) { statusText.Append("Deactivation reason: "); @@ -31,29 +27,28 @@ public void Run(Configure config) foreach (var reason in diagnosticData.PrerequisiteStatus.Reasons) { - statusText.AppendLine(" -"+ reason); - + statusText.AppendLine(" -" + reason); } - } - else if (!diagnosticData.DependenciesAreMeet) + } + else if (!diagnosticData.DependenciesAreMet) { - statusText.AppendLine(string.Format("Did not meet one of the dependencies: {0}", String.Join(",", diagnosticData.Dependencies.Select(t => "[" + String.Join(",", t.Select(t1 => t1)) + "]")))); + statusText.AppendLine($"Did not meet one of the dependencies: {string.Join(",", diagnosticData.Dependencies.Select(t => "[" + string.Join(",", t.Select(t1 => t1)) + "]"))}"); } else { - statusText.AppendLine("Not explicitly enabled"); + statusText.AppendLine("Not explicitly enabled"); } } else { - statusText.AppendLine(string.Format("Dependencies: {0}", diagnosticData.Dependencies.Count == 0 ? "None" : String.Join(",", diagnosticData.Dependencies.Select(t => "[" + String.Join(",", t.Select(t1 => t1)) + "]")))); - statusText.AppendLine(string.Format("Startup Tasks: {0}", diagnosticData.StartupTasks.Count == 0 ? "None" : String.Join(",", diagnosticData.StartupTasks.Select(t => t.Name)))); + statusText.AppendLine($"Dependencies: {(diagnosticData.Dependencies.Count == 0 ? "Default" : string.Join(",", diagnosticData.Dependencies.Select(t => "[" + string.Join(",", t.Select(t1 => t1)) + "]")))}"); + statusText.AppendLine($"Startup Tasks: {(diagnosticData.StartupTasks.Count == 0 ? "Default" : string.Join(",", diagnosticData.StartupTasks.Select(t => t)))}"); } statusText.AppendLine(); } - Logger.Info(statusText.ToString()); + Logger.Debug(statusText.ToString()); } static ILog Logger = LogManager.GetLogger(); diff --git a/src/NServiceBus.Core/Features/EndpointConfigurationExtensions.cs b/src/NServiceBus.Core/Features/EndpointConfigurationExtensions.cs new file mode 100644 index 00000000000..f2a7f7617b6 --- /dev/null +++ b/src/NServiceBus.Core/Features/EndpointConfigurationExtensions.cs @@ -0,0 +1,57 @@ +namespace NServiceBus +{ + using System; + using Features; + + /// + /// Extension methods declarations. + /// + public static class EndpointConfigurationExtensions + { + /// + /// Enables the given feature. + /// + /// The instance to apply the settings to. + public static void EnableFeature(this EndpointConfiguration config) where T : Feature + { + Guard.AgainstNull(nameof(config), config); + config.EnableFeature(typeof(T)); + } + + /// + /// Enables the given feature. + /// + /// The instance to apply the settings to. + /// The feature to enable. + public static void EnableFeature(this EndpointConfiguration config, Type featureType) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(featureType), featureType); + + config.Settings.EnableFeature(featureType); + } + + /// + /// Disables the given feature. + /// + /// The instance to apply the settings to. + public static void DisableFeature(this EndpointConfiguration config) where T : Feature + { + Guard.AgainstNull(nameof(config), config); + config.DisableFeature(typeof(T)); + } + + /// + /// Enables the given feature. + /// + /// The instance to apply the settings to. + /// The feature to disable. + public static void DisableFeature(this EndpointConfiguration config, Type featureType) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(featureType), featureType); + + config.Settings.DisableFeature(featureType); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/ExposeSettings.cs b/src/NServiceBus.Core/Features/ExposeSettings.cs index 4f33c8c104c..a84ae8cb3e1 100644 --- a/src/NServiceBus.Core/Features/ExposeSettings.cs +++ b/src/NServiceBus.Core/Features/ExposeSettings.cs @@ -1,22 +1,23 @@ namespace NServiceBus.Configuration.AdvanceExtensibility { - using NServiceBus.Settings; + using Settings; /// - /// Base class that exposes for extensibility. + /// Base class that exposes for extensibility. /// public abstract class ExposeSettings { /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// protected ExposeSettings(SettingsHolder settings) { + Guard.AgainstNull(nameof(settings), settings); Settings = settings; } /// - /// Get the current this wraps. + /// Get the current this wraps. /// internal SettingsHolder Settings { get; private set; } } diff --git a/src/NServiceBus.Core/Features/Feature.cs b/src/NServiceBus.Core/Features/Feature.cs index 96845782a63..4bbe6c657e3 100644 --- a/src/NServiceBus.Core/Features/Feature.cs +++ b/src/NServiceBus.Core/Features/Feature.cs @@ -6,81 +6,67 @@ using Settings; /// - /// Used to control the various features supported by the framework. + /// Used to control the various features supported by the framework. /// - public abstract class Feature + public abstract partial class Feature { /// - /// Creates an instance of . + /// Creates an instance of . /// protected Feature() { - StartupTasks = new List(); Dependencies = new List>(); Name = GetFeatureName(GetType()); } /// - /// Feature name. + /// Feature name. /// - public string Name { get; private set; } + public string Name { get; } /// - /// The version for this feature + /// The version for this feature. /// - public string Version - { - get { return FileVersionRetriever.GetFileVersion(GetType()); } - } + public string Version => FileVersionRetriever.GetFileVersion(GetType()); /// - /// The list of features that this feature is depending on + /// The list of features that this feature is depending on. /// - internal List> Dependencies { get; private set; } + internal List> Dependencies { get; } /// - /// Tells if this feature is enabled by default + /// Tells if this feature is enabled by default. /// public bool IsEnabledByDefault { get; private set; } /// - /// Indicates that the feature is active + /// Indicates that the feature is active. /// public bool IsActive { get; private set; } - internal List StartupTasks { get; private set; } - /// - /// Registers default settings + /// Registers default settings. /// - /// The settings holder + /// The settings holder. protected void Defaults(Action settings) - { - defaults.Add(settings); - } - - /// - /// Access to the registered defaults - /// - internal List> RegisteredDefaults { get { return defaults; } } + { + registeredDefaults.Add(settings); + } /// - /// Called when the features is activated + /// Called when the features is activated. /// protected internal abstract void Setup(FeatureConfigurationContext context); /// - /// Adds a setup prerequisite condition. If false this feature won't be setup. - /// Prerequisites are only evaluated if the feature is enabled. + /// Adds a setup prerequisite condition. If false this feature won't be setup. + /// Prerequisites are only evaluated if the feature is enabled. /// /// Condition that must be met in order for this feature to be activated. /// Explanation of what this prerequisite checks. - protected void Prerequisite(Func condition,string description) + protected void Prerequisite(Func condition, string description) { - if (string.IsNullOrEmpty(description)) - { - throw new ArgumentException("Description can't be empty", "description"); - } + Guard.AgainstNullAndEmpty(nameof(description), description); setupPrerequisites.Add(new SetupPrerequisite { @@ -90,7 +76,7 @@ protected void Prerequisite(Func condition,st } /// - /// Marks this feature as enabled by default. + /// Marks this feature as enabled by default. /// protected void EnableByDefault() { @@ -98,9 +84,9 @@ protected void EnableByDefault() } /// - /// Registers this feature as depending on the given feature. This means that this feature won't be activated unless - /// the dependant feature is active. - /// This also causes this feature to be activated after the other feature. + /// Registers this feature as depending on the given feature. This means that this feature won't be activated unless + /// the dependant feature is active. + /// This also causes this feature to be activated after the other feature. /// /// Feature that this feature depends on. protected void DependsOn() where T : Feature @@ -109,35 +95,33 @@ protected void DependsOn() where T : Feature } /// - /// Registers this feature as depending on the given feature. This means that this feature won't be activated unless - /// the dependant feature is active. - /// This also causes this feature to be activated after the other feature. + /// Registers this feature as depending on the given feature. This means that this feature won't be activated unless + /// the dependant feature is active. This also causes this feature to be activated after the other feature. /// - /// The name of the feature that this feature depends on. - protected void DependsOn(string featureName) + /// The of the feature that this feature depends on. + protected void DependsOn(string featureTypeName) { - Dependencies.Add(new List{featureName}); + Dependencies.Add(new List + { + featureTypeName + }); } /// - /// Register this feature as depending on at least on of the given features. This means that this feature won't be - /// activated - /// unless at least one of the provided features in the list is active. - /// This also causes this feature to be activated after the other features. + /// Register this feature as depending on at least on of the given features. This means that this feature won't be + /// activated unless at least one of the provided features in the list is active. + /// This also causes this feature to be activated after the other features. /// /// Features list that this feature require at least one of to be activated. protected void DependsOnAtLeastOne(params Type[] features) { - if (features == null) - { - throw new ArgumentNullException("features"); - } + Guard.AgainstNull(nameof(features), features); foreach (var feature in features) { if (!feature.IsSubclassOf(baseFeatureType)) { - throw new ArgumentException(string.Format("A Feature can only depend on another Feature. '{0}' is not a Feature", feature.FullName), "features"); + throw new ArgumentException($"A Feature can only depend on another Feature. '{feature.FullName}' is not a Feature", nameof(features)); } } @@ -145,40 +129,59 @@ protected void DependsOnAtLeastOne(params Type[] features) } /// - /// Register this feature as depending on at least on of the given features. This means that this feature won't be - /// activated unless at least one of the provided features in the list is active. - /// This also causes this feature to be activated after the other features. + /// Registers this feature as optionally depending on the given feature. It means that the declaring feature's + /// method will be called + /// after the dependent feature's if that dependent feature is enabled. /// - /// The name of the features that this feature depends on. - protected void DependsOnAtLeastOne(params string[] featureNames) + /// The name of the feature that this feature depends on. + protected void DependsOnOptionally(string featureName) { - if (featureNames == null) - { - throw new ArgumentNullException("featureNames"); - } + DependsOnAtLeastOne(GetFeatureName(typeof(RootFeature)), featureName); + } - Dependencies.Add(new List(featureNames)); + /// + /// Registers this feature as optionally depending on the given feature. It means that the declaring feature's + /// method will be called + /// after the dependent feature's if that dependent feature is enabled. + /// + /// The type of the feature that this feature depends on. + protected void DependsOnOptionally(Type featureType) + { + Guard.AgainstNull(nameof(featureType), featureType); + + DependsOnOptionally(GetFeatureName(featureType)); } /// - /// that is executed when the is started. + /// Registers this feature as optionally depending on the given feature. It means that the declaring feature's + /// method will be called + /// after the dependent feature's if that dependent feature is enabled. /// - /// A . - protected void RegisterStartupTask() where T : FeatureStartupTask + /// The type of the feature that this feature depends on. + protected void DependsOnOptionally() where T : Feature { - StartupTasks.Add(typeof(T)); + DependsOnOptionally(typeof(T)); + } + + /// + /// Register this feature as depending on at least on of the given features. This means that this feature won't be + /// activated unless at least one of the provided features in the list is active. + /// This also causes this feature to be activated after the other features. + /// + /// The name of the features that this feature depends on. + protected void DependsOnAtLeastOne(params string[] featureNames) + { + Guard.AgainstNull(nameof(featureNames), featureNames); + + Dependencies.Add(new List(featureNames)); } /// - /// Returns a string that represents the current object. + /// Returns a string that represents the current object. /// - /// - /// A string that represents the current object. - /// - /// 2 public override string ToString() { - return string.Format("{0} [{1}]", Name, Version); + return $"{Name} [{Version}]"; } internal PrerequisiteStatus CheckPrerequisites(FeatureConfigurationContext context) @@ -203,30 +206,29 @@ internal void SetupFeature(FeatureConfigurationContext config) IsActive = true; } - static string GetFeatureName(Type featureType) + internal void ConfigureDefaults(SettingsHolder settings) { - var name = featureType.Name; - - if (name.EndsWith("Feature")) + foreach (var registeredDefault in registeredDefaults) { - if (name.Length > featureStringLength) - { - name = name.Substring(0, name.Length - featureStringLength); - } + registeredDefault(settings); } + } - return name; + static string GetFeatureName(Type featureType) + { + return featureType.FullName; } + readonly List> registeredDefaults = new List>(); + readonly List setupPrerequisites = new List(); + static Type baseFeatureType = typeof(Feature); static int featureStringLength = "Feature".Length; - List setupPrerequisites = new List(); - List> defaults = new List>(); class SetupPrerequisite { - public string Description; public Func Condition; + public string Description; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureActivator.cs b/src/NServiceBus.Core/Features/FeatureActivator.cs index 7283c53b47c..d0da2eb12b8 100644 --- a/src/NServiceBus.Core/Features/FeatureActivator.cs +++ b/src/NServiceBus.Core/Features/FeatureActivator.cs @@ -3,75 +3,10 @@ namespace NServiceBus.Features using System; using System.Collections.Generic; using System.Linq; - using NServiceBus.ObjectBuilder; - using NServiceBus.Settings; - - /// - /// Provides diagnostics data about s. - /// - public class FeaturesReport - { - internal FeaturesReport(IEnumerable data) - { - this.data = data.ToList().AsReadOnly(); - } - - /// - /// List of s diagnostic data. - /// - public IList Features - { - get { return data; } - } - - readonly IList data; - } - - /// - /// diagnostics data. - /// - public class FeatureDiagnosticData - { - /// - /// Gets the name. - /// - public string Name { get; internal set; } - - /// - /// Gets whether is set to be enabled by default. - /// - public bool EnabledByDefault { get; internal set; } - - /// - /// Gets the status of the . - /// - public bool Active { get; internal set; } - - /// - /// Gets the status of the prerequisites for this . - /// - public PrerequisiteStatus PrerequisiteStatus { get; internal set; } - - /// - /// Gets the list of s that this depends on. - /// - public IList> Dependencies { get; internal set; } - - /// - /// Gets the version. - /// - public string Version { get; internal set; } - - /// - /// Gets the startup tasks. - /// - public IList StartupTasks { get; internal set; } - - /// - /// Gets whether all dependant s are activated. - /// - public bool DependenciesAreMeet { get; set; } - } + using System.Threading.Tasks; + using ObjectBuilder; + using Pipeline; + using Settings; class FeatureActivator { @@ -92,73 +27,64 @@ public void Add(Feature feature) settings.EnableFeatureByDefault(feature.GetType()); } - features.Add(new FeatureState(feature, new FeatureDiagnosticData + features.Add(new FeatureInfo(feature, new FeatureDiagnosticData { EnabledByDefault = feature.IsEnabledByDefault, Name = feature.Name, Version = feature.Version, - StartupTasks = feature.StartupTasks.AsReadOnly(), - Dependencies = feature.Dependencies.AsReadOnly(), + Dependencies = feature.Dependencies.AsReadOnly() })); } - public FeaturesReport SetupFeatures(FeatureConfigurationContext context) + public FeaturesReport SetupFeatures(IConfigureComponents container, PipelineSettings pipelineSettings) { - var featuresToActivate = Sort(features).Where(featureState => IsEnabled(featureState.Feature.GetType())); + // featuresToActivate is enumerated twice because after setting defaults some new features might got activated. + var sourceFeatures = Sort(features); - foreach (var defaultSetting in featuresToActivate.SelectMany(feature => feature.Feature.RegisteredDefaults)) + var enabledFeatures = new List(); + while (true) { - defaultSetting(settings); + var featureToActivate = sourceFeatures.FirstOrDefault(x => settings.IsFeatureEnabled(x.Feature.GetType())); + if (featureToActivate == null) + { + break; + } + sourceFeatures.Remove(featureToActivate); + enabledFeatures.Add(featureToActivate); + featureToActivate.Feature.ConfigureDefaults(settings); } - settings.PreventChanges(); - - foreach (var feature in featuresToActivate) + foreach (var feature in enabledFeatures) { - ActivateFeature(feature, featuresToActivate, context); + ActivateFeature(feature, enabledFeatures, container, pipelineSettings); } - return new FeaturesReport(features.Select(t => t.Diagnostics)); - } + settings.PreventChanges(); - public void RegisterStartupTasks(IConfigureComponents container) - { - foreach (var feature in features.Where(f => f.Feature.IsActive)) - { - foreach (var taskType in feature.Feature.StartupTasks) - { - container.ConfigureComponent(taskType, DependencyLifecycle.SingleInstance); - } - } + return new FeaturesReport(features.Select(t => t.Diagnostics).ToList()); } - public void StartFeatures(IBuilder builder) + public async Task StartFeatures(IBuilder builder, IMessageSession session) { foreach (var feature in features.Where(f => f.Feature.IsActive)) { - foreach (var taskType in feature.Feature.StartupTasks) + foreach (var taskController in feature.TaskControllers) { - var task = (FeatureStartupTask) builder.Build(taskType); - - task.PerformStartup(); + await taskController.Start(builder, session).ConfigureAwait(false); } } } - public void StopFeatures(IBuilder builder) + public Task StopFeatures(IMessageSession session) { - foreach (var feature in features.Where(f => f.Feature.IsActive)) - { - foreach (var taskType in feature.Feature.StartupTasks) - { - var task = (FeatureStartupTask) builder.Build(taskType); + var featureStopTasks = features.Where(f => f.Feature.IsActive) + .SelectMany(f => f.TaskControllers) + .Select(task => task.Stop(session)); - task.PerformStop(); - } - } + return Task.WhenAll(featureStopTasks); } - static IEnumerable Sort(IEnumerable features) + static List Sort(IEnumerable features) { // Step 1: create nodes for graph var nameToNodeDict = new Dictionary(); @@ -189,25 +115,62 @@ static IEnumerable Sort(IEnumerable features) } // Step 3: Perform Topological Sort - var output = new List(); + var output = new List(); foreach (var node in allNodes) { node.Visit(output); } + // Step 4: DFS to check if we have an directed acyclic graph + foreach (var node in allNodes) + { + if (DirectedCycleExistsFrom(node, new Node[] + { + })) + { + throw new ArgumentException("Cycle in dependency graph detected"); + } + } + return output; } - static bool ActivateFeature(FeatureState featureState, IEnumerable featuresToActivate, FeatureConfigurationContext context) + static bool DirectedCycleExistsFrom(Node node, Node[] visitedNodes) + { + if (node.previous.Any()) + { + if (visitedNodes.Any(n => n == node)) + { + return true; + } + + var newVisitedNodes = visitedNodes.Union(new[] + { + node + }).ToArray(); + + foreach (var subNode in node.previous) + { + if (DirectedCycleExistsFrom(subNode, newVisitedNodes)) + { + return true; + } + } + } + + return false; + } + + bool ActivateFeature(FeatureInfo featureInfo, List featuresToActivate, IConfigureComponents container, PipelineSettings pipelineSettings) { - if (featureState.Feature.IsActive) + if (featureInfo.Feature.IsActive) { return true; } Func, bool> dependencyActivator = dependencies => { - var dependantFeaturesToActivate = new List(); + var dependantFeaturesToActivate = new List(); foreach (var dependency in dependencies.Select(dependencyName => featuresToActivate .SingleOrDefault(f => f.Feature.Name == dependencyName)) @@ -215,36 +178,31 @@ static bool ActivateFeature(FeatureState featureState, IEnumerable { dependantFeaturesToActivate.Add(dependency); } - var hasAllUpstreamDepsBeenActivated = dependantFeaturesToActivate.Aggregate(false, (current, f) => current | ActivateFeature(f, featuresToActivate, context)); - - return hasAllUpstreamDepsBeenActivated; + return dependantFeaturesToActivate.Aggregate(false, (current, f) => current | ActivateFeature(f, featuresToActivate, container, pipelineSettings)); }; - - if (featureState.Feature.Dependencies.All(dependencyActivator)) + var featureType = featureInfo.Feature.GetType(); + if (featureInfo.Feature.Dependencies.All(dependencyActivator)) { - featureState.Diagnostics.DependenciesAreMeet = true; + featureInfo.Diagnostics.DependenciesAreMet = true; - if (!HasAllPrerequisitesSatisfied(featureState.Feature, featureState.Diagnostics, context)) + var context = new FeatureConfigurationContext(settings, container, pipelineSettings); + if (!HasAllPrerequisitesSatisfied(featureInfo.Feature, featureInfo.Diagnostics, context)) { + settings.MarkFeatureAsDeactivated(featureType); return false; } - - featureState.Feature.SetupFeature(context); - featureState.Diagnostics.Active = true; - + settings.MarkFeatureAsActive(featureType); + featureInfo.Feature.SetupFeature(context); + featureInfo.TaskControllers = context.TaskControllers; + featureInfo.Diagnostics.StartupTasks = context.TaskControllers.Select(d => d.Name).ToList(); + featureInfo.Diagnostics.Active = true; return true; } - - featureState.Diagnostics.DependenciesAreMeet = false; - + settings.MarkFeatureAsDeactivated(featureType); + featureInfo.Diagnostics.DependenciesAreMet = false; return false; } - bool IsEnabled(Type featureType) - { - return settings.GetOrDefault(featureType.FullName); - } - static bool HasAllPrerequisitesSatisfied(Feature feature, FeatureDiagnosticData diagnosticData, FeatureConfigurationContext context) { diagnosticData.PrerequisiteStatus = feature.CheckPrerequisites(context); @@ -252,24 +210,30 @@ static bool HasAllPrerequisitesSatisfied(Feature feature, FeatureDiagnosticData return diagnosticData.PrerequisiteStatus.IsSatisfied; } - readonly List features = new List(); - readonly SettingsHolder settings; + List features = new List(); + SettingsHolder settings; - class FeatureState + class FeatureInfo { - public FeatureState(Feature feature, FeatureDiagnosticData diagnostics) + public FeatureInfo(Feature feature, FeatureDiagnosticData diagnostics) { Diagnostics = diagnostics; Feature = feature; } - public FeatureDiagnosticData Diagnostics { get; private set; } - public Feature Feature { get; private set; } + public FeatureDiagnosticData Diagnostics { get; } + public Feature Feature { get; } + public IReadOnlyList TaskControllers { get; set; } + + public override string ToString() + { + return $"{Feature.Name} [{Feature.Version}]"; + } } class Node { - internal void Visit(ICollection output) + internal void Visit(ICollection output) { if (visited) { @@ -283,27 +247,9 @@ internal void Visit(ICollection output) output.Add(FeatureState); } - internal FeatureState FeatureState; + internal FeatureInfo FeatureState; internal List previous = new List(); bool visited; } - - class Runner : IWantToRunWhenBusStartsAndStops - { - public IBuilder Builder { get; set; } - - public FeatureActivator FeatureActivator { get; set; } - - - public void Start() - { - FeatureActivator.StartFeatures(Builder); - } - - public void Stop() - { - FeatureActivator.StopFeatures(Builder); - } - } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureConfigurationContext.cs b/src/NServiceBus.Core/Features/FeatureConfigurationContext.cs index f4a2dccc275..036f24c5bfe 100644 --- a/src/NServiceBus.Core/Features/FeatureConfigurationContext.cs +++ b/src/NServiceBus.Core/Features/FeatureConfigurationContext.cs @@ -1,34 +1,88 @@ namespace NServiceBus.Features { + using System; + using System.Collections.Generic; + using System.Threading.Tasks; using ObjectBuilder; using Pipeline; using Settings; + using Transport; /// - /// The context available to features when they are activated + /// The context available to features when they are activated. /// public class FeatureConfigurationContext { - readonly Configure config; + internal FeatureConfigurationContext(ReadOnlySettings settings, IConfigureComponents container, PipelineSettings pipelineSettings) + { + Settings = settings; + Container = container; + Pipeline = pipelineSettings; + + TaskControllers = new List(); + } + + /// + /// A read only copy of the settings. + /// + public ReadOnlySettings Settings { get; } + + /// + /// Access to the container to allow for registrations. + /// + public IConfigureComponents Container { get; } + + /// + /// Access to the pipeline in order to customize it. + /// + public PipelineSettings Pipeline { get; } + + internal List TaskControllers { get; } - internal FeatureConfigurationContext(Configure config) + /// + /// Adds a new satellite receiver. + /// + /// Name of the satellite. + /// Minimum required transaction mode. + /// Transport runtime settings. + /// The message func. + /// The autogenerated transport address to listen on. + /// Recoverability policy to be if processing fails. + public void AddSatelliteReceiver(string name, string transportAddress, TransportTransactionMode requiredTransportTransactionMode, PushRuntimeSettings runtimeSettings, Func recoverabilityPolicy, Func onMessage) { - this.config = config; + var satelliteDefinition = new SatelliteDefinition(name, transportAddress, requiredTransportTransactionMode, runtimeSettings, recoverabilityPolicy, onMessage); + + Settings.Get().Add(satelliteDefinition); + + Settings.Get().BindReceiving(transportAddress); } /// - /// A read only copy of the settings + /// Registers an instance of a feature startup task. /// - public ReadOnlySettings Settings { get { return config.Settings; } } - + /// A startup task. + public void RegisterStartupTask(TTask startupTask) where TTask : FeatureStartupTask + { + RegisterStartupTask(() => startupTask); + } + /// - /// Access to the container to allow for registrations + /// Registers a startup task factory. /// - public IConfigureComponents Container { get { return config.configurer; } } - + /// A startup task factory. + public void RegisterStartupTask(Func startupTaskFactory) where TTask : FeatureStartupTask + { + TaskControllers.Add(new FeatureStartupTaskController(typeof(TTask).Name, _ => startupTaskFactory())); + } + /// - /// Access to the pipeline in order to customize it + /// Registers a startup task factory which gets access to the builder. /// - public PipelineSettings Pipeline { get { return config.pipeline; } } + /// A startup task factory. + /// Should only be used when really necessary. Usually a design smell. + public void RegisterStartupTask(Func startupTaskFactory) where TTask : FeatureStartupTask + { + TaskControllers.Add(new FeatureStartupTaskController(typeof(TTask).Name, startupTaskFactory)); + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureDiagnosticData.cs b/src/NServiceBus.Core/Features/FeatureDiagnosticData.cs new file mode 100644 index 00000000000..5b643c5ffe2 --- /dev/null +++ b/src/NServiceBus.Core/Features/FeatureDiagnosticData.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Features +{ + using System.Collections.Generic; + + class FeatureDiagnosticData + { + public string Name { get; internal set; } + + public bool EnabledByDefault { get; internal set; } + + public bool Active { get; internal set; } + + public PrerequisiteStatus PrerequisiteStatus { get; internal set; } + + public IReadOnlyList> Dependencies { get; internal set; } + + public string Version { get; internal set; } + + public IReadOnlyList StartupTasks { get; internal set; } + + public bool DependenciesAreMet { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureRunner.cs b/src/NServiceBus.Core/Features/FeatureRunner.cs new file mode 100644 index 00000000000..40a9328c98e --- /dev/null +++ b/src/NServiceBus.Core/Features/FeatureRunner.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Features +{ + using System.Threading.Tasks; + using ObjectBuilder; + + class FeatureRunner + { + public FeatureRunner(FeatureActivator featureActivator) + { + this.featureActivator = featureActivator; + } + + public Task Start(IBuilder builder, IMessageSession messageSession) + { + return featureActivator.StartFeatures(builder, messageSession); + } + + public Task Stop(IMessageSession messageSession) + { + return featureActivator.StopFeatures(messageSession); + } + + FeatureActivator featureActivator; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureStartupTask.cs b/src/NServiceBus.Core/Features/FeatureStartupTask.cs index e8f787f1b5e..df72913904f 100644 --- a/src/NServiceBus.Core/Features/FeatureStartupTask.cs +++ b/src/NServiceBus.Core/Features/FeatureStartupTask.cs @@ -1,28 +1,34 @@ namespace NServiceBus.Features { + using System.Threading.Tasks; + /// /// Base for feature startup tasks. /// public abstract class FeatureStartupTask { /// - /// Will be called when the endpoint starts up if the feature has been activated. + /// Will be called after an endpoint has been started but before processing any messages, if the feature has been + /// activated. /// - protected abstract void OnStart(); - + /// Bus session. + protected abstract Task OnStart(IMessageSession session); + /// - /// Will be called when the endpoint stops and the feature is active. + /// Will be called after an endpoint has been started but before processing any messages, if the feature has been + /// activated. /// - protected virtual void OnStop(){} - - internal void PerformStartup() + /// Bus session. + protected abstract Task OnStop(IMessageSession session); + + internal Task PerformStartup(IMessageSession session) { - OnStart(); + return OnStart(session); } - internal void PerformStop() + internal Task PerformStop(IMessageSession session) { - OnStop(); + return OnStop(session); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureStartupTaskController.cs b/src/NServiceBus.Core/Features/FeatureStartupTaskController.cs new file mode 100644 index 00000000000..101f70a6fae --- /dev/null +++ b/src/NServiceBus.Core/Features/FeatureStartupTaskController.cs @@ -0,0 +1,38 @@ +namespace NServiceBus.Features +{ + using System; + using System.Threading.Tasks; + using ObjectBuilder; + + class FeatureStartupTaskController + { + public FeatureStartupTaskController(string name, Func factory) + { + Name = name; + this.factory = factory; + } + + public string Name { get; } + + public Task Start(IBuilder builder, IMessageSession messageSession) + { + instance = factory(builder); + return instance.PerformStartup(messageSession); + } + + public async Task Stop(IMessageSession messageSession) + { + await instance.PerformStop(messageSession).ConfigureAwait(false); + DisposeIfNecessary(instance); + } + + static void DisposeIfNecessary(FeatureStartupTask task) + { + var disposableTask = task as IDisposable; + disposableTask?.Dispose(); + } + + Func factory; + FeatureStartupTask instance; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeatureState.cs b/src/NServiceBus.Core/Features/FeatureState.cs new file mode 100644 index 00000000000..94c1051d137 --- /dev/null +++ b/src/NServiceBus.Core/Features/FeatureState.cs @@ -0,0 +1,28 @@ +namespace NServiceBus.Features +{ + /// + /// Defines state of a feature. + /// + public enum FeatureState + { + /// + /// Not selected for activation. + /// + Disabled, + + /// + /// Selected for activation. + /// + Enabled, + + /// + /// Activated. + /// + Active, + + /// + /// Activation not possible. + /// + Deactivated + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/FeaturesReport.cs b/src/NServiceBus.Core/Features/FeaturesReport.cs new file mode 100644 index 00000000000..fd8d378167a --- /dev/null +++ b/src/NServiceBus.Core/Features/FeaturesReport.cs @@ -0,0 +1,14 @@ +namespace NServiceBus.Features +{ + using System.Collections.Generic; + + class FeaturesReport + { + internal FeaturesReport(IReadOnlyList data) + { + Features = data; + } + + public IReadOnlyList Features { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/PrerequisiteStatus.cs b/src/NServiceBus.Core/Features/PrerequisiteStatus.cs index e89eb1afdf0..ad581800d5d 100644 --- a/src/NServiceBus.Core/Features/PrerequisiteStatus.cs +++ b/src/NServiceBus.Core/Features/PrerequisiteStatus.cs @@ -2,10 +2,7 @@ namespace NServiceBus.Features { using System.Collections.Generic; - /// - /// The prerequisite status of a feature - /// - public class PrerequisiteStatus + class PrerequisiteStatus { internal PrerequisiteStatus() { @@ -13,15 +10,9 @@ internal PrerequisiteStatus() IsSatisfied = true; } - /// - /// True if all prerequisites for the feature is satisfied - /// public bool IsSatisfied { get; private set; } - /// - /// The list of reason why the prerequisites are not fullfilled if applicable - /// - public List Reasons { get; private set; } + public List Reasons { get; } internal void ReportFailure(string description) { diff --git a/src/NServiceBus.Core/Features/RootFeature.cs b/src/NServiceBus.Core/Features/RootFeature.cs new file mode 100644 index 00000000000..7888e0b5425 --- /dev/null +++ b/src/NServiceBus.Core/Features/RootFeature.cs @@ -0,0 +1,17 @@ +namespace NServiceBus.Features +{ + /// + /// A root feature that is always enabled. + /// + class RootFeature : Feature + { + public RootFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/SatelliteDefinitions.cs b/src/NServiceBus.Core/Features/SatelliteDefinitions.cs new file mode 100644 index 00000000000..800cb5eb42d --- /dev/null +++ b/src/NServiceBus.Core/Features/SatelliteDefinitions.cs @@ -0,0 +1,14 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + + class SatelliteDefinitions + { + public List Definitions { get; } = new List(); + + public void Add(SatelliteDefinition satelliteDefinition) + { + Definitions.Add(satelliteDefinition); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/SettingsExtensions.cs b/src/NServiceBus.Core/Features/SettingsExtensions.cs new file mode 100644 index 00000000000..7cf7f3a81c5 --- /dev/null +++ b/src/NServiceBus.Core/Features/SettingsExtensions.cs @@ -0,0 +1,68 @@ +namespace NServiceBus.Features +{ + using System; + using Settings; + + /// + /// Feature related extensions to the settings. + /// + public static class SettingsExtensions + { + /// + /// Marks the given feature as enabled by default. + /// + public static SettingsHolder EnableFeatureByDefault(this SettingsHolder settings) where T : Feature + { + Guard.AgainstNull(nameof(settings), settings); + settings.EnableFeatureByDefault(typeof(T)); + return settings; + } + + /// + /// Marks the given feature as enabled by default. + /// + public static SettingsHolder EnableFeatureByDefault(this SettingsHolder settings, Type featureType) + { + Guard.AgainstNull(nameof(settings), settings); + Guard.AgainstNull(nameof(featureType), featureType); + settings.SetDefault(featureType.FullName, FeatureState.Enabled); + return settings; + } + + /// + /// Returns if a given feature has been activated in this endpoint. + /// + public static bool IsFeatureActive(this ReadOnlySettings settings, Type featureType) + { + return settings.GetOrDefault(featureType.FullName) == FeatureState.Active; + } + + /// + /// Returns if a given feature has been enabled in this endpoint. + /// + public static bool IsFeatureEnabled(this ReadOnlySettings settings, Type featureType) + { + return settings.GetOrDefault(featureType.FullName) == FeatureState.Enabled; + } + + internal static void EnableFeature(this SettingsHolder settings, Type featureType) + { + settings.Set(featureType.FullName, FeatureState.Enabled); + } + + internal static void DisableFeature(this SettingsHolder settings, Type featureType) + { + settings.Set(featureType.FullName, FeatureState.Disabled); + } + + internal static void MarkFeatureAsActive(this SettingsHolder settings, Type featureType) + { + settings.Set(featureType.FullName, FeatureState.Active); + } + + internal static void MarkFeatureAsDeactivated(this SettingsHolder settings, Type featureType) + { + settings.Set(featureType.FullName, FeatureState.Deactivated); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Features/SettingsExtentions.cs b/src/NServiceBus.Core/Features/SettingsExtentions.cs deleted file mode 100644 index cd3a9523ef1..00000000000 --- a/src/NServiceBus.Core/Features/SettingsExtentions.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using Settings; - - /// - /// Feature related extentions to the settings - /// - public static class SettingsExtentions - { - /// - /// Marks the given feature as enabled by default - /// - public static SettingsHolder EnableFeatureByDefault(this SettingsHolder settings) where T : Feature - { - settings.EnableFeatureByDefault(typeof(T)); - return settings; - } - - /// - /// Marks the given feature as enabled by default - /// - public static SettingsHolder EnableFeatureByDefault(this SettingsHolder settings, Type featureType) - { - settings.SetDefault(featureType.FullName, true); - return settings; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/FodyWeavers.xml b/src/NServiceBus.Core/FodyWeavers.xml index 3ed853064a3..32e89da61e0 100644 --- a/src/NServiceBus.Core/FodyWeavers.xml +++ b/src/NServiceBus.Core/FodyWeavers.xml @@ -1,5 +1,5 @@ - + - + \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/ConfigureForwarding.cs b/src/NServiceBus.Core/Forwarding/ConfigureForwarding.cs new file mode 100644 index 00000000000..e68b4e1ffa5 --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/ConfigureForwarding.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + /// + /// Contains extension methods to . + /// + public static class ConfigureForwarding + { + /// + /// Sets the address to which received messages will be forwarded. + /// + /// The instance to apply the settings to. + /// The address to forward successfully processed messages to. + public static void ForwardReceivedMessagesTo(this EndpointConfiguration config, string address) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(address), address); + config.Settings.Set(SettingsKey, address); + } + + internal const string SettingsKey = "forwardReceivedMessagesTo"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/ForwardReceivedMessages.cs b/src/NServiceBus.Core/Forwarding/ForwardReceivedMessages.cs new file mode 100644 index 00000000000..607ac26d69b --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/ForwardReceivedMessages.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Features +{ + using Transport; + + /// + /// Provides message forwarding capabilities. + /// + public class ForwardReceivedMessages : Feature + { + internal ForwardReceivedMessages() + { + EnableByDefault(); + + Prerequisite(config => config.Settings.HasSetting(ConfigureForwarding.SettingsKey), "No forwarding address was defined in the UnicastBus config"); + } + + /// + /// Invoked if the feature is activated. + /// + /// The feature context. + protected internal override void Setup(FeatureConfigurationContext context) + { + var forwardReceivedMessagesQueue = context.Settings.Get(ConfigureForwarding.SettingsKey); + + context.Settings.Get().BindSending(forwardReceivedMessagesQueue); + + context.Pipeline.Register("InvokeForwardingPipeline", new InvokeForwardingPipelineBehavior(forwardReceivedMessagesQueue), "Execute the forwarding pipeline"); + + context.Pipeline.Register(new ForwardingToRoutingConnector(), "Makes sure that forwarded messages gets dispatched to the transport"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/ForwardingContext.cs b/src/NServiceBus.Core/Forwarding/ForwardingContext.cs new file mode 100644 index 00000000000..25d2531f625 --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/ForwardingContext.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using Pipeline; + using Transport; + + class ForwardingContext : BehaviorContext, IForwardingContext + { + public ForwardingContext(OutgoingMessage messageToForward, string address, IBehaviorContext parentContext) : base(parentContext) + { + Guard.AgainstNull(nameof(messageToForward), messageToForward); + Guard.AgainstNullAndEmpty(nameof(address), address); + Message = messageToForward; + Address = address; + } + + public OutgoingMessage Message { get; } + + public string Address { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/ForwardingToRoutingConnector.cs b/src/NServiceBus.Core/Forwarding/ForwardingToRoutingConnector.cs new file mode 100644 index 00000000000..944543a9436 --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/ForwardingToRoutingConnector.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Routing; + + class ForwardingToRoutingConnector : StageConnector + { + public override Task Invoke(IForwardingContext context, Func stage) + { + return stage(this.CreateRoutingContext(context.Message, new UnicastRoutingStrategy(context.Address), context)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/IForwardingContext.cs b/src/NServiceBus.Core/Forwarding/IForwardingContext.cs new file mode 100644 index 00000000000..764c14cb835 --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/IForwardingContext.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Pipeline +{ + using Transport; + + /// + /// Provide context to behaviors on the forwarding pipeline. + /// + public interface IForwardingContext : IBehaviorContext + { + /// + /// The message to be forwarded. + /// + OutgoingMessage Message { get; } + + /// + /// The address of the forwarding queue. + /// + string Address { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Forwarding/InvokeForwardingPipelineBehavior.cs b/src/NServiceBus.Core/Forwarding/InvokeForwardingPipelineBehavior.cs new file mode 100644 index 00000000000..9b3c28da354 --- /dev/null +++ b/src/NServiceBus.Core/Forwarding/InvokeForwardingPipelineBehavior.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class InvokeForwardingPipelineBehavior : IForkConnector + { + public InvokeForwardingPipelineBehavior(string forwardingAddress) + { + this.forwardingAddress = forwardingAddress; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + await next(context).ConfigureAwait(false); + + context.Message.RevertToOriginalBodyIfNeeded(); + + var processedMessage = new OutgoingMessage(context.Message.MessageId, context.Message.Headers, context.Message.Body); + + var forwardingContext = this.CreateForwardingContext(processedMessage, forwardingAddress, context); + + await this.Fork(forwardingContext).ConfigureAwait(false); + } + + string forwardingAddress; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Gateway/IDeduplicateMessages.cs b/src/NServiceBus.Core/Gateway/IDeduplicateMessages.cs index 7975bd45f6b..8c00863455b 100644 --- a/src/NServiceBus.Core/Gateway/IDeduplicateMessages.cs +++ b/src/NServiceBus.Core/Gateway/IDeduplicateMessages.cs @@ -1,17 +1,20 @@ namespace NServiceBus.Gateway.Deduplication { using System; + using System.Threading.Tasks; + using Extensibility; /// - /// Defines the api for storages that wants to provide storage for gateway deduplication + /// Defines the api for storages that wants to provide storage for gateway deduplication. /// public interface IDeduplicateMessages { /// - /// Returns true if the message is a duplicate + /// Returns true if the message is a duplicate. /// - /// The client id that defines the range of ids to check for duplicates - /// The time received of the message to allow the storage to do cleanup - bool DeduplicateMessage(string clientId, DateTime timeReceived); + /// The client id that defines the range of ids to check for duplicates. + /// The time received of the message to allow the storage to do cleanup. + /// The current pipeline context. + Task DeduplicateMessage(string clientId, DateTime timeReceived, ContextBag context); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/GitFlowVersion.cs b/src/NServiceBus.Core/GitFlowVersion.cs index ac925f1ef52..d48ad542c85 100644 --- a/src/NServiceBus.Core/GitFlowVersion.cs +++ b/src/NServiceBus.Core/GitFlowVersion.cs @@ -7,9 +7,9 @@ static class GitFlowVersion static GitFlowVersion() { var assembly = typeof(GitFlowVersion).Assembly; - var gitFlowVersionInformationType = assembly.GetType("GitVersionInformation", true); + var gitFlowVersionInformationType = assembly.GetType("NServiceBus.GitVersionInformation", true); var fieldInfo = gitFlowVersionInformationType.GetField("MajorMinorPatch"); - var majorMinorPatchVersion = Version.Parse((string)fieldInfo.GetValue(null)); + var majorMinorPatchVersion = Version.Parse((string) fieldInfo.GetValue(null)); MajorMinor = majorMinorPatchVersion.ToString(2); MajorMinorPatch = majorMinorPatchVersion.ToString(3); } diff --git a/src/NServiceBus.Core/HandlerOrdering/First.cs b/src/NServiceBus.Core/HandlerOrdering/First.cs deleted file mode 100644 index b88fc16800d..00000000000 --- a/src/NServiceBus.Core/HandlerOrdering/First.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - - /// - /// Used to indicate the order in which handler types are to run. - /// - /// Not thread safe. - /// - /// The type which will run first. - public class First - { - /// - /// Specifies the type which will run next. - /// - public static First Then() - { - var instance = new First(); - - instance.AndThen(); - instance.AndThen(); - - return instance; - } - - /// - /// Returns the ordered list of types specified. - /// - public IEnumerable Types - { - get { return types; } - } - - /// - /// Specifies the type which will run next - /// - public First AndThen() - { - if (!types.Contains(typeof(K))) - types.Add(typeof(K)); - - return this; - } - - List types = new List(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/HandlerOrdering/ISpecifyMessageHandlerOrdering.cs b/src/NServiceBus.Core/HandlerOrdering/ISpecifyMessageHandlerOrdering.cs deleted file mode 100644 index f407a30f9b4..00000000000 --- a/src/NServiceBus.Core/HandlerOrdering/ISpecifyMessageHandlerOrdering.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus -{ - /// - /// Specify the order in which message handlers will be invoked. - /// - public interface ISpecifyMessageHandlerOrdering - { - /// - /// In this method, use the order object to specify the order - /// in which message handlers will be activated. - /// - void SpecifyOrder(Order order); - } -} diff --git a/src/NServiceBus.Core/HandlerOrdering/Order.cs b/src/NServiceBus.Core/HandlerOrdering/Order.cs deleted file mode 100644 index 7fa698cc327..00000000000 --- a/src/NServiceBus.Core/HandlerOrdering/Order.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - - /// - /// Used to specify the order in which message handlers will be activated. - /// - public class Order - { - /// - /// Gets the types whose order has been specified. - /// - public IEnumerable Types { get; set; } - - - /// - /// Specifies that the given type will be activated before all others. - /// - public void SpecifyFirst() - { - Types = new[] {typeof (T)}; - } - - /// - /// Specifies an ordering of multiple types using the syntax: First{H1}.Then{H2}().AndThen{H3}() etc - /// - public void Specify(First ordering) - { - Types = ordering.Types; - } - - /// - /// Specifies an ordering of multiple types directly, where ordering may be decided dynamically at runtime. - /// - public void Specify(params Type[] priorityHandlers) - { - if (priorityHandlers == null) - { - throw new ArgumentNullException("priorityHandlers"); - } - Types = priorityHandlers; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Headers.cs b/src/NServiceBus.Core/Headers.cs index 01211cf6564..22116174770 100644 --- a/src/NServiceBus.Core/Headers.cs +++ b/src/NServiceBus.Core/Headers.cs @@ -1,11 +1,9 @@ namespace NServiceBus { - using System; - /// /// Static class containing headers used by NServiceBus. /// - public static class Headers + public static partial class Headers { /// /// Header for retrieving from which Http endpoint the message arrived. @@ -31,7 +29,7 @@ public static class Headers public const string DestinationSites = "NServiceBus.DestinationSites"; /// - /// Header for specifying the key for the site where this message originated. + /// Header for specifying the key for the site where this message originated. /// This header is considered an applicative header. /// public const string OriginatingSite = "NServiceBus.OriginatingSite"; @@ -42,13 +40,6 @@ public static class Headers /// public const string SagaId = "NServiceBus.SagaId"; - /// - /// Header containing a list of saga types and ids that this message invoked, the format is "{sagatype}={sagaid};{sagatype}={sagaid}" - /// This header is considered an applicative header. - /// - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.1", Message = "Enriching the headers for saga related information has been moved to the SagaAudit plugin in ServiceControl. Add a reference to the Saga audit plugin in your endpoint to get more information.")] - public const string InvokedSagas = "NServiceBus.InvokedSagas"; - /// /// Header containing a stable message id for a message. /// @@ -63,74 +54,102 @@ public static class Headers /// Header containing the ReplyToAddress for a message. /// public const string ReplyToAddress = "NServiceBus.ReplyToAddress"; - + /// /// Prefix included on the wire when sending applicative headers. /// public const string HeaderName = "Header"; - /// - /// Header containing the windows identity name - /// - public const string WindowsIdentityName = "WinIdName"; - /// /// Header telling the NServiceBus Version (beginning NServiceBus V3.0.1). /// public const string NServiceBusVersion = "NServiceBus.Version"; /// - /// Used in a header when doing a callback (bus.return) + /// Used in a header when doing a callback (session.return). /// public const string ReturnMessageErrorCodeHeader = "NServiceBus.ReturnMessage.ErrorCode"; /// - /// Header that tells if this transport message is a control message + /// Header that tells if this transport message is a control message. /// public const string ControlMessageHeader = "NServiceBus.ControlMessage"; /// - /// Type of the saga that this message is targeted for + /// Type of the saga that this message is targeted for. /// public const string SagaType = "NServiceBus.SagaType"; /// - /// Id of the saga that sent this message + /// Id of the saga that sent this message. /// public const string OriginatingSagaId = "NServiceBus.OriginatingSagaId"; /// - /// Type of the saga that sent this message + /// Type of the saga that sent this message. /// public const string OriginatingSagaType = "NServiceBus.OriginatingSagaType"; /// - /// The number of second-level retries that has been performed for this message + /// The number of Delayed Retries that have been performed for this message. /// - public const string Retries = "NServiceBus.Retries"; + public const string DelayedRetries = "NServiceBus.Retries"; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = nameof(DelayedRetries) + )] +#pragma warning disable 1591 + public const string Retries = DelayedRetries; +#pragma warning restore 1591 + + /// + /// The time the last Delayed Retry has been performed for this message. + /// + public const string DelayedRetriesTimestamp = "NServiceBus.Retries.Timestamp"; + + + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = nameof(DelayedRetriesTimestamp) + )] +#pragma warning disable 1591 + public const string RetriesTimestamp = DelayedRetriesTimestamp; +#pragma warning restore 1591 /// - /// The number of first-level retries that has been performed for this message + /// The number of Immediate Retries that have been performed for this message. /// - public const string FLRetries = "NServiceBus.FLRetries"; + public const string ImmediateRetries = "NServiceBus.FLRetries"; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = nameof(ImmediateRetries) + )] +#pragma warning disable 1591 + public const string FLRetries = ImmediateRetries; +#pragma warning restore 1591 /// - /// The time processing of this message started + /// The time processing of this message started. /// public const string ProcessingStarted = "NServiceBus.ProcessingStarted"; /// - /// The time processing of this message ended + /// The time processing of this message ended. /// public const string ProcessingEnded = "NServiceBus.ProcessingEnded"; /// - /// The time this message was sent from the client + /// The time this message was sent from the client. /// public const string TimeSent = "NServiceBus.TimeSent"; /// - /// Id of the message that caused this message to be sent + /// Id of the message that caused this message to be sent. /// public const string RelatedTo = "NServiceBus.RelatedTo"; @@ -140,47 +159,58 @@ public static class Headers public const string EnclosedMessageTypes = "NServiceBus.EnclosedMessageTypes"; /// - /// Header entry key indicating format of the payload + /// Header entry key indicating format of the payload. /// public const string ContentType = "NServiceBus.ContentType"; /// - /// Header entry key for the given message type that is being subscribed to, when message intent is subscribe or unsubscribe. + /// Header entry key for the given message type that is being subscribed to, when message intent is subscribe or + /// unsubscribe. /// public const string SubscriptionMessageType = "SubscriptionMessageType"; /// - /// True if this message is a saga timeout + /// Header entry key for the transport address of the subscribing endpoint. + /// + public const string SubscriberTransportAddress = "NServiceBus.SubscriberAddress"; + + /// + /// Header entry key for the logical name of the subscribing endpoint. + /// + public const string SubscriberEndpoint = "NServiceBus.SubscriberEndpoint"; + + /// + /// True if this message is a saga timeout. /// public const string IsSagaTimeoutMessage = "NServiceBus.IsSagaTimeoutMessage"; /// - /// True if this is a deferred message + /// True if this is a deferred message. /// public const string IsDeferredMessage = "NServiceBus.IsDeferredMessage"; /// - /// Name of the endpoint where the given message originated + /// Name of the endpoint where the given message originated. /// public const string OriginatingEndpoint = "NServiceBus.OriginatingEndpoint"; /// - /// Machine name of the endpoint where the given message originated + /// Machine name of the endpoint where the given message originated. /// public const string OriginatingMachine = "NServiceBus.OriginatingMachine"; /// - /// HostId of the endpoint where the given message originated + /// HostId of the endpoint where the given message originated. /// public const string OriginatingHostId = "$.diagnostics.originating.hostid"; /// - /// Name of the endpoint where the given message was processed (success or failure) + /// Name of the endpoint where the given message was processed (success or failure). /// public const string ProcessingEndpoint = "NServiceBus.ProcessingEndpoint"; /// - /// Machine name of the endpoint where the given message was processed (success or failure) + /// Machine name of the endpoint where the given message was processed (success or failure). /// public const string ProcessingMachine = "NServiceBus.ProcessingMachine"; @@ -190,67 +220,45 @@ public static class Headers public const string HostDisplayName = "$.diagnostics.hostdisplayname"; /// - /// HostId of the endpoint where the given message was processed (success or failure) + /// HostId of the endpoint where the given message was processed (success or failure). /// public const string HostId = "$.diagnostics.hostid"; /// - /// HostId of the endpoint where the given message was processed (success or failure) + /// HostId of the endpoint where the given message was processed (success or failure). /// public const string HasLicenseExpired = "$.diagnostics.license.expired"; /// - /// The original reply to address for successfully processed messages + /// The original reply to address for successfully processed messages. /// public const string OriginatingAddress = "NServiceBus.OriginatingAddress"; /// - /// The id of the message conversation that this message is part of + /// The id of the message conversation that this message is part of. /// public const string ConversationId = "NServiceBus.ConversationId"; /// - /// The intent of the current message + /// The intent of the current message. /// public const string MessageIntent = "NServiceBus.MessageIntent"; /// - /// The identifier to lookup the key to decrypt the encrypted data. + /// The identifier to lookup the key to decrypt the encrypted data. /// public const string RijndaelKeyIdentifier = "NServiceBus.RijndaelKeyIdentifier"; /// - /// Get the header with the given key. Cannot be used to change its value. + /// The time to be received for this message when it was sent the first time. + /// When moved to error and audit this header will be preserved to the original TTBR + /// of the message can be known. /// - /// The message to retrieve a header from. - /// The header key. - /// The value assigned to the header. - [ObsoleteEx( - Replacement = "bus.GetMessageHeader(msg, key)", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] -// ReSharper disable UnusedParameter.Global - public static string GetMessageHeader(object msg, string key) -// ReSharper restore UnusedParameter.Global - { - throw new InvalidOperationException(); - } - - /// - /// Sets the value of the header for the given key. - /// - /// The message to add a header to. - /// The header key. - /// The value to assign to the header. - [ObsoleteEx( - Replacement = "bus.SetMessageHeader(msg, key, value)", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] -// ReSharper disable UnusedParameter.Global - public static void SetMessageHeader(object msg, string key, string value) -// ReSharper restore UnusedParameter.Global - { - throw new InvalidOperationException(); - } + public const string TimeToBeReceived = "NServiceBus.TimeToBeReceived"; + + /// + /// Indicates that the message was sent as a non-durable message. + /// + public const string NonDurableMessage = "NServiceBus.NonDurableMessage"; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/AddHostInfoHeadersBehavior.cs b/src/NServiceBus.Core/Hosting/AddHostInfoHeadersBehavior.cs new file mode 100644 index 00000000000..0c0cb0e173e --- /dev/null +++ b/src/NServiceBus.Core/Hosting/AddHostInfoHeadersBehavior.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Hosting; + using Pipeline; + using Support; + + class AddHostInfoHeadersBehavior : IBehavior + { + public AddHostInfoHeadersBehavior(HostInformation hostInformation, string endpoint) + { + this.hostInformation = hostInformation; + this.endpoint = endpoint; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + context.Headers[Headers.OriginatingMachine] = RuntimeEnvironment.MachineName; + context.Headers[Headers.OriginatingEndpoint] = endpoint; + context.Headers[Headers.OriginatingHostId] = hostInformation.HostId.ToString("N"); + + return next(context); + } + + string endpoint; + HostInformation hostInformation; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/AuditHostInformationBehavior.cs b/src/NServiceBus.Core/Hosting/AuditHostInformationBehavior.cs new file mode 100644 index 00000000000..47688bc3842 --- /dev/null +++ b/src/NServiceBus.Core/Hosting/AuditHostInformationBehavior.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Hosting; + using Pipeline; + using Support; + + class AuditHostInformationBehavior : IBehavior + { + public AuditHostInformationBehavior(HostInformation hostInfo, string endpoint) + { + this.hostInfo = hostInfo; + this.endpoint = endpoint; + } + + public Task Invoke(IAuditContext context, Func next) + { + context.AddAuditData(Headers.HostId, hostInfo.HostId.ToString("N")); + context.AddAuditData(Headers.HostDisplayName, hostInfo.DisplayName); + + context.AddAuditData(Headers.ProcessingMachine, RuntimeEnvironment.MachineName); + context.AddAuditData(Headers.ProcessingEndpoint, endpoint); + + return next(context); + } + + string endpoint; + + HostInformation hostInfo; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/Configuration/EndpointNameAttribute.cs b/src/NServiceBus.Core/Hosting/Configuration/EndpointNameAttribute.cs deleted file mode 100644 index b5fe2d61f23..00000000000 --- a/src/NServiceBus.Core/Hosting/Configuration/EndpointNameAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Used to specify the name of the current endpoint. - /// Will be used as the name of the input queue as well. - /// - public sealed class EndpointNameAttribute : Attribute - { - /// - /// Used to specify the name of the current endpoint. - /// Will be used as the name of the input queue as well. - /// - public EndpointNameAttribute(string name) - { - Name = name; - } - - /// - /// The name of the endpoint. - /// - public string Name { get; set; } - } -} diff --git a/src/NServiceBus.Core/Hosting/Configuration/IConfigureThisEndpoint.cs b/src/NServiceBus.Core/Hosting/Configuration/IConfigureThisEndpoint.cs deleted file mode 100644 index 6a3bb8235f7..00000000000 --- a/src/NServiceBus.Core/Hosting/Configuration/IConfigureThisEndpoint.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus -{ - /// - /// Indicate that the implementing class will specify configuration. - /// - public interface IConfigureThisEndpoint - { - /// - /// Allows to override default settings. - /// - /// Endpoint configuration builder. -// ReSharper disable once UnusedParameter.Global - void Customize(BusConfiguration configuration); - } -} diff --git a/src/NServiceBus.Core/Hosting/Configuration/IWantCustomInitialization.cs b/src/NServiceBus.Core/Hosting/Configuration/IWantCustomInitialization.cs deleted file mode 100644 index d6a3d8c1cd3..00000000000 --- a/src/NServiceBus.Core/Hosting/Configuration/IWantCustomInitialization.cs +++ /dev/null @@ -1,12 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Please use `INeedInitialization` or `IConfigureThisEndpoint`")] - public interface IWantCustomInitialization - { - void Init(); - } -} diff --git a/src/NServiceBus.Core/Hosting/Configuration/Logging_Obsolete.cs b/src/NServiceBus.Core/Hosting/Configuration/Logging_Obsolete.cs deleted file mode 100644 index 0bd84f73c74..00000000000 --- a/src/NServiceBus.Core/Hosting/Configuration/Logging_Obsolete.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable 1591 - -namespace NServiceBus -{ - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - Message = "Use the `NServiceBus.Hosting.Profiles.IConfigureLogging` interface which is contained with in the `NServiceBus.Host` nuget. ", - RemoveInVersion = "6.0")] - public interface IConfigureLogging - { - } - - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - Message = "Use the `NServiceBus.Hosting.Profiles.IConfigureLoggingForProfile` interface which is contained with in the `NServiceBus.Host` nuget.", - RemoveInVersion = "6.0")] - public interface IConfigureLoggingForProfile - { - } - - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - Message = "Configure logging in the constructor of the class that implements IConfigureThisEndpoint.", - RemoveInVersion = "6.0")] - public interface IWantCustomLogging - { - } -} diff --git a/src/NServiceBus.Core/Hosting/Helpers/AssemblyScanner.cs b/src/NServiceBus.Core/Hosting/Helpers/AssemblyScanner.cs index fca758eb68f..4f5680962a2 100644 --- a/src/NServiceBus.Core/Hosting/Helpers/AssemblyScanner.cs +++ b/src/NServiceBus.Core/Hosting/Helpers/AssemblyScanner.cs @@ -7,14 +7,15 @@ namespace NServiceBus.Hosting.Helpers using System.Reflection; using System.Runtime.CompilerServices; using System.Text; + using Logging; /// - /// Helpers for assembly scanning operations + /// Helpers for assembly scanning operations. /// - public class AssemblyScanner + public partial class AssemblyScanner { /// - /// Creates a new scanner that will scan the base directory of the current appdomain + /// Creates a new scanner that will scan the base directory of the current appdomain. /// public AssemblyScanner() : this(AppDomain.CurrentDomain.BaseDirectory) @@ -22,42 +23,50 @@ public AssemblyScanner() } /// - /// Creates a scanner for the given directory + /// Creates a scanner for the given directory. /// - /// public AssemblyScanner(string baseDirectoryToScan) { ThrowExceptions = true; - AssembliesToInclude = new List(); - AssembliesToSkip = new List(); - MustReferenceAtLeastOneAssembly = new List(); this.baseDirectoryToScan = baseDirectoryToScan; } + internal AssemblyScanner(Assembly assemblyToScan) + { + this.assemblyToScan = assemblyToScan; + ThrowExceptions = true; + } + /// - /// Tells the scanner to only include assemblies that reference one of the given assemblies + /// Determines if the scanner should throw exceptions or not. /// - public List MustReferenceAtLeastOneAssembly { get; private set; } - - + public bool ThrowExceptions { get; set; } /// - /// Traverses the specified base directory including all sub-directories, generating a list of assemblies that can be - /// scanned for handlers, a list of skipped files, and a list of errors that occurred while scanning. - /// Scanned files may be skipped when they're either not a .NET assembly, or if a reflection-only load of the .NET - /// assembly reveals that it does not reference NServiceBus. + /// Traverses the specified base directory including all sub-directories, generating a list of assemblies that can be + /// scanned for handlers, a list of skipped files, and a list of errors that occurred while scanning. + /// Scanned files may be skipped when they're either not a .NET assembly, or if a reflection-only load of the .NET + /// assembly reveals that it does not reference NServiceBus. /// public AssemblyScannerResults GetScannableAssemblies() { var results = new AssemblyScannerResults(); + if (assemblyToScan != null) + { + var assemblyPath = AssemblyPath(assemblyToScan); + ScanAssembly(assemblyPath, results); + return results; + } + if (IncludeAppDomainAssemblies) { - var matchingAssembliesFromAppDomain = AppDomain.CurrentDomain - .GetAssemblies() - .Where(assembly => IsIncluded(assembly.GetName().Name)); + var matchingAssembliesFromAppDomain = MatchingAssembliesFromAppDomain(); - results.Assemblies.AddRange(matchingAssembliesFromAppDomain); + foreach (var assembly in matchingAssembliesFromAppDomain) + { + ScanAssembly(AssemblyPath(assembly), results); + } } foreach (var assemblyFile in ScanDirectoryForAssemblyFiles()) @@ -65,13 +74,44 @@ public AssemblyScannerResults GetScannableAssemblies() ScanAssembly(assemblyFile.FullName, results); } + // This extra step is to ensure unobtrusive message types are included in the Types list. + var list = GetHandlerMessageTypes(results.Types).ToList(); + results.Types.AddRange(list); + + results.RemoveDuplicates(); + return results; } - /// - /// Determines if the scanner should throw exceptions or not - /// - public bool ThrowExceptions { get; set; } + static IEnumerable GetHandlerMessageTypes(IEnumerable list) + { + return list.SelectMany(type => + { + if (type.IsAbstract || type.IsGenericTypeDefinition) + { + return Type.EmptyTypes; + } + return type.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == IHandleMessagesType); + }) + .Select(t => + { + var args = t.GetGenericArguments(); + return args[0]; + }); + } + + List MatchingAssembliesFromAppDomain() + { + return AppDomain.CurrentDomain + .GetAssemblies() + .Where(assembly => !assembly.IsDynamic && IsIncluded(assembly.GetName().Name)).ToList(); + } + + static string AssemblyPath(Assembly assembly) + { + var uri = new UriBuilder(assembly.CodeBase); + return Uri.UnescapeDataString(uri.Path).Replace('/', '\\'); + } void ScanAssembly(string assemblyPath, AssemblyScannerResults results) { @@ -101,13 +141,13 @@ void ScanAssembly(string assemblyPath, AssemblyScannerResults results) try { - //TODO: re-enable when we make message scanning lazy #1617 - //if (!AssemblyPassesReferencesTest(assemblyPath)) - //{ - // var skippedFile = new SkippedFile(assemblyPath, "Assembly does not reference at least one of the must referenced assemblies."); - // results.SkippedFiles.Add(skippedFile); - // return; - //} + if (!ReferencesNServiceBus(assemblyPath)) + { + var skippedFile = new SkippedFile(assemblyPath, "Assembly does not reference at least one of the must referenced assemblies."); + results.SkippedFiles.Add(skippedFile); + return; + } + if (IsRuntimeAssembly(assemblyPath)) { var skippedFile = new SkippedFile(assemblyPath, "Assembly .net runtime assembly."); @@ -129,7 +169,7 @@ void ScanAssembly(string assemblyPath, AssemblyScannerResults results) if (ThrowExceptions) { - var errorMessage = String.Format("Could not load '{0}'. Consider excluding that assembly from the scanning.", assemblyPath); + var errorMessage = $"Could not load '{assemblyPath}'. Consider excluding that assembly from the scanning."; throw new Exception(errorMessage, ex); } } @@ -142,64 +182,76 @@ void ScanAssembly(string assemblyPath, AssemblyScannerResults results) try { //will throw if assembly cannot be loaded - assembly.GetTypes(); + results.Types.AddRange(assembly.GetTypes().Where(IsAllowedType)); } catch (ReflectionTypeLoadException e) { results.ErrorsThrownDuringScanning = true; + var errorMessage = FormatReflectionTypeLoadException(assemblyPath, e); if (ThrowExceptions) { - var errorMessage = FormatReflectionTypeLoadException(assemblyPath, e); throw new Exception(errorMessage); } - return; + LogManager.GetLogger().Warn(errorMessage); + results.Types.AddRange(e.Types.Where(IsAllowedType)); } results.Assemblies.Add(assembly); } - internal static bool IsRuntimeAssembly(string assemblyPath) { - var publicKeyToken = AssemblyName.GetAssemblyName(assemblyPath).GetPublicKeyToken(); - var lowerInvariant = BitConverter.ToString(publicKeyToken).Replace("-", "").ToLowerInvariant(); + var assemblyName = AssemblyName.GetAssemblyName(assemblyPath); + return IsRuntimeAssembly(assemblyName); + } + + internal static bool IsRuntimeAssembly(AssemblyName assemblyName) + { + var publicKeyToken = assemblyName.GetPublicKeyToken(); + var lowerInvariant = BitConverter.ToString(publicKeyToken).Replace("-", string.Empty).ToLowerInvariant(); //System if (lowerInvariant == "b77a5c561934e089") { return true; } + //System.Core + mscorelib + if (lowerInvariant == "7cec85d7bea7798e") + { + return true; + } + //Web if (lowerInvariant == "b03f5f7f11d50a3a") { return true; } - + //patterns and practices if (lowerInvariant == "31bf3856ad364e35") { return true; } - + return false; } - internal static string FormatReflectionTypeLoadException(string fileName, ReflectionTypeLoadException e) + static string FormatReflectionTypeLoadException(string fileName, ReflectionTypeLoadException e) { var sb = new StringBuilder(); - sb.AppendLine(string.Format("Could not enumerate all types for '{0}'.", fileName)); + sb.AppendLine($"Could not enumerate all types for '{fileName}'."); if (!e.LoaderExceptions.Any()) { - sb.AppendLine(string.Format("Exception message: {0}", e)); + sb.AppendLine($"Exception message: {e}"); return sb.ToString(); } var nsbAssemblyName = typeof(AssemblyScanner).Assembly.GetName(); - var nsbPublicKeyToken = BitConverter.ToString(nsbAssemblyName.GetPublicKeyToken()).Replace("-", "").ToLowerInvariant(); + var nsbPublicKeyToken = BitConverter.ToString(nsbAssemblyName.GetPublicKeyToken()).Replace("-", string.Empty).ToLowerInvariant(); var displayBindingRedirects = false; var files = new List(); var sbFileLoadException = new StringBuilder(); @@ -212,7 +264,7 @@ internal static string FormatReflectionTypeLoadException(string fileName, Reflec if (loadException?.FileName != null) { var assemblyName = new AssemblyName(loadException.FileName); - var assemblyPublicKeyToken = BitConverter.ToString(assemblyName.GetPublicKeyToken()).Replace("-", "").ToLowerInvariant(); + var assemblyPublicKeyToken = BitConverter.ToString(assemblyName.GetPublicKeyToken()).Replace("-", string.Empty).ToLowerInvariant(); if (nsbAssemblyName.Name == assemblyName.Name && nsbAssemblyName.CultureInfo.ToString() == assemblyName.CultureInfo.ToString() && nsbPublicKeyToken == assemblyPublicKeyToken) @@ -240,7 +292,7 @@ internal static string FormatReflectionTypeLoadException(string fileName, Reflec if (sbFileLoadException.ToString().Length > 0) { - sb.AppendLine("It looks like you may be missing binding redirects in your config file for the following assemblies:"); + sb.AppendLine("It looks like you may be missing binding redirects in the config file for the following assemblies:"); sb.Append(sbFileLoadException); sb.AppendLine("For more information see http://msdn.microsoft.com/en-us/library/7wd6ex19(v=vs.100).aspx"); } @@ -248,7 +300,7 @@ internal static string FormatReflectionTypeLoadException(string fileName, Reflec if (displayBindingRedirects) { sb.AppendLine(); - sb.AppendLine("Try to add the following binding redirects to your config file:"); + sb.AppendLine("Try to add the following binding redirects to the config file:"); const string bindingRedirects = @" @@ -284,24 +336,59 @@ IEnumerable GetFileSearchPatternsToUse() } } - bool AssemblyPassesReferencesTest(string assemblyPath) + internal static bool ReferencesNServiceBus(string assemblyPath) { - if (MustReferenceAtLeastOneAssembly.Count == 0) + var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath); + //TODO: should we seed the results with NServiceBus.Core.dll? + if (assembly.GetName().Name == "NServiceBus.Core") { return true; } - var lightLoad = Assembly.ReflectionOnlyLoadFrom(assemblyPath); - var referencedAssemblies = lightLoad.GetReferencedAssemblies(); + return ReferencesNServiceBus(assembly, new List()); + } - return MustReferenceAtLeastOneAssembly - .Select(reference => reference.GetName().Name) - .Any(name => referencedAssemblies.Any(a => a.Name == name)); + static bool ReferencesNServiceBus(Assembly assembly, List processed) + { + foreach (var assemblyName in assembly.GetReferencedAssemblies()) + { + if (assemblyName.Name == "NServiceBus.Core") + { + return true; + } + + if (processed.Any(x => x.FullName == assemblyName.FullName)) + { + continue; + } + processed.Add(assemblyName); + + if (IsRuntimeAssembly(assemblyName)) + { + continue; + } + //We need to do a ApplyPolicy, and use the result, so as to respect the current binding redirects + var afterPolicyName = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName); + Assembly refAssembly; + try + { + refAssembly = Assembly.ReflectionOnlyLoad(afterPolicyName); + } + catch (FileNotFoundException) + { + continue; + } + catch (FileLoadException) + { + continue; + } + if (ReferencesNServiceBus(refAssembly, processed)) + { + return true; + } + } + return false; } - /// - /// Determines whether the specified assembly name or file name can be included, given the set up include/exclude - /// patterns and default include/exclude patterns - /// bool IsIncluded(string assemblyNameOrFileName) { var isExplicitlyExcluded = AssembliesToSkip.Any(excluded => IsMatch(excluded, assemblyNameOrFileName)); @@ -316,32 +403,20 @@ bool IsIncluded(string assemblyNameOrFileName) return false; } - var noAssembliesWereExplicitlyIncluded = !AssembliesToInclude.Any(); - var isExplicitlyIncluded = AssembliesToInclude.Any(included => IsMatch(included, assemblyNameOrFileName)); - - return noAssembliesWereExplicitlyIncluded || isExplicitlyIncluded; + return true; } - static bool IsMatch(string expression, string scopedNameOrFileName) + static bool IsMatch(string expression1, string expression2) { - if (DistillLowerAssemblyName(scopedNameOrFileName).StartsWith(expression.ToLower())) - { - return true; - } - - if (DistillLowerAssemblyName(expression).TrimEnd('.') == DistillLowerAssemblyName(scopedNameOrFileName)) - { - return true; - } - - return false; + return DistillLowerAssemblyName(expression1) == DistillLowerAssemblyName(expression2); } - internal static bool IsAllowedType(Type type) + bool IsAllowedType(Type type) { return type != null && !type.IsValueType && - !(type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length > 0); + !(type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length > 0) && + !TypesToSkip.Contains(type); } static string DistillLowerAssemblyName(string assemblyOrFileName) @@ -354,43 +429,36 @@ static string DistillLowerAssemblyName(string assemblyOrFileName) return lowerAssemblyName; } + internal List AssembliesToSkip = new List(); + Assembly assemblyToScan; string baseDirectoryToScan; - internal List AssembliesToInclude; - internal List AssembliesToSkip; - internal bool IncludeAppDomainAssemblies; internal bool IncludeExesInScan = true; - internal bool ScanNestedDirectories = true; - + internal bool ScanNestedDirectories; + internal List TypesToSkip = new List(); //TODO: delete when we make message scanning lazy #1617 static string[] DefaultAssemblyExclusions = - { - - "system.", - "mscorlib.", - - // NSB Build-Dependencies - "nunit.", "pnunit.", "rhino.mocks.", "XsdGenerator.", - - // NSB OSS Dependencies - "rhino.licensing.", "bouncycastle.crypto", - "magnum.", "interop.", "nlog.", "newtonsoft.json.", - "common.logging.", "topshelf.", - "Autofac.", "log4net.", "nhibernate.", - "castle.", - - // Raven - "raven.server", "raven.client", "raven.munin.", - "raven.storage.", "raven.abstractions.", "raven.database", - "esent.interop", "asyncctplibrary.", "lucene.net.", - "icsharpcode.nrefactory", "spatial4n.core", - - // Azure host process, which is typically referenced for ease of deployment but should not be scanned - "NServiceBus.Hosting.Azure.HostProcess.exe", - - // And other windows azure stuff - "Microsoft.WindowsAzure." - - }; + { + // NSB Build-Dependencies + "nunit", + + // NSB OSS Dependencies + "nlog", + "newtonsoft.json", + "common.logging", + "nhibernate", + + // Raven + "raven.client", + "raven.abstractions", + + // Azure host process, which is typically referenced for ease of deployment but should not be scanned + "NServiceBus.Hosting.Azure.HostProcess.exe", + + // And other windows azure stuff + "Microsoft.WindowsAzure" + }; + + static Type IHandleMessagesType = typeof(IHandleMessages<>); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/Helpers/AssemblyScannerResults.cs b/src/NServiceBus.Core/Hosting/Helpers/AssemblyScannerResults.cs index 542c92efe53..9c8002c6a98 100644 --- a/src/NServiceBus.Core/Hosting/Helpers/AssemblyScannerResults.cs +++ b/src/NServiceBus.Core/Hosting/Helpers/AssemblyScannerResults.cs @@ -1,38 +1,52 @@ namespace NServiceBus.Hosting.Helpers { + using System; using System.Collections.Generic; + using System.Linq; using System.Reflection; /// - /// Holds GetScannableAssemblies results. - /// Contains list of errors and list of scan-able assemblies. + /// Holds results. + /// Contains list of errors and list of scannable assemblies. /// - public class AssemblyScannerResults + public class AssemblyScannerResults { /// - /// Constructor to initialize AssemblyScannerResults + /// Constructor to initialize AssemblyScannerResults. /// public AssemblyScannerResults() { Assemblies = new List(); + Types = new List(); SkippedFiles = new List(); } - + /// - /// List of successfully found and loaded assemblies + /// List of successfully found and loaded assemblies. /// public List Assemblies { get; private set; } - + /// /// List of files that were skipped while scanning because they were a) explicitly excluded /// by the user, b) not a .NET DLL, or c) not referencing NSB and thus not capable of implementing - /// + /// . /// public List SkippedFiles { get; private set; } /// - /// True if errors where encountered during assembly scanning + /// True if errors where encountered during assembly scanning. /// public bool ErrorsThrownDuringScanning { get; internal set; } + + /// + /// List of types. + /// + public List Types { get; private set; } + + internal void RemoveDuplicates() + { + Assemblies = Assemblies.Distinct().ToList(); + Types = Types.Distinct().ToList(); + } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/Helpers/Image.cs b/src/NServiceBus.Core/Hosting/Helpers/Image.cs index 5eae7c59782..d2e00ff24fa 100644 --- a/src/NServiceBus.Core/Hosting/Helpers/Image.cs +++ b/src/NServiceBus.Core/Hosting/Helpers/Image.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Hosting.Helpers +namespace NServiceBus { using System; using System.IO; @@ -31,8 +31,6 @@ namespace NServiceBus.Hosting.Helpers // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class Image : IDisposable { - Stream stream; - public enum CompilationMode { NativeOrInvalid, @@ -40,39 +38,54 @@ public enum CompilationMode CLRx64 } + Image(Stream stream) + { + this.stream = stream; + } + + public void Dispose() + { + } + public static CompilationMode GetCompilationMode(string file) { - if (file == null) - { - throw new ArgumentNullException("file", "You must specify a file name"); - } + Guard.AgainstNull(nameof(file), file); using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var image = new Image(stream)) { - return image.GetCompilationMode(); + using (var image = new Image(stream)) + { + return image.GetCompilationMode(); + } } } - Image(Stream stream) - { - this.stream = stream; - } - CompilationMode GetCompilationMode() { if (stream.Length < 318) + { return CompilationMode.NativeOrInvalid; + } if (ReadUInt16() != 0x5a4d) + { return CompilationMode.NativeOrInvalid; + } if (!Advance(58)) + { return CompilationMode.NativeOrInvalid; + } if (!MoveTo(ReadUInt32())) + { return CompilationMode.NativeOrInvalid; + } if (ReadUInt32() != 0x00004550) + { return CompilationMode.NativeOrInvalid; + } if (!Advance(20)) + { return CompilationMode.NativeOrInvalid; + } var result = CompilationMode.NativeOrInvalid; switch (ReadUInt16()) @@ -103,7 +116,9 @@ CompilationMode GetCompilationMode() bool Advance(int length) { if (stream.Position + length >= stream.Length) + { return false; + } stream.Seek(length, SeekOrigin.Current); return true; @@ -112,28 +127,28 @@ bool Advance(int length) bool MoveTo(uint position) { if (position >= stream.Length) + { return false; + } stream.Position = position; return true; } - public void Dispose() - { - } - ushort ReadUInt16() { - return (ushort)(stream.ReadByte() - | (stream.ReadByte() << 8)); + return (ushort) (stream.ReadByte() + | (stream.ReadByte() << 8)); } uint ReadUInt32() { - return (uint)(stream.ReadByte() - | (stream.ReadByte() << 8) - | (stream.ReadByte() << 16) - | (stream.ReadByte() << 24)); + return (uint) (stream.ReadByte() + | (stream.ReadByte() << 8) + | (stream.ReadByte() << 16) + | (stream.ReadByte() << 24)); } + + Stream stream; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/Helpers/SkippedFile.cs b/src/NServiceBus.Core/Hosting/Helpers/SkippedFile.cs index 0617dcdb50e..b85d5d121e0 100644 --- a/src/NServiceBus.Core/Hosting/Helpers/SkippedFile.cs +++ b/src/NServiceBus.Core/Hosting/Helpers/SkippedFile.cs @@ -2,7 +2,7 @@ { /// /// Contains information about a file that was skipped during scanning along with a text describing - /// the reason why the file was skipped + /// the reason why the file was skipped. /// public class SkippedFile { @@ -13,12 +13,12 @@ internal SkippedFile(string filePath, string message) } /// - /// The full path to the file that was skipped + /// The full path to the file that was skipped. /// public string FilePath { get; private set; } - + /// - /// Description of the reason why this file was skipped + /// Description of the reason why this file was skipped. /// public string SkipReason { get; private set; } } diff --git a/src/NServiceBus.Core/Hosting/HostInfoConfigurationExtensions.cs b/src/NServiceBus.Core/Hosting/HostInfoConfigurationExtensions.cs index ff9a3d4e7be..0f6c8c3be1f 100644 --- a/src/NServiceBus.Core/Hosting/HostInfoConfigurationExtensions.cs +++ b/src/NServiceBus.Core/Hosting/HostInfoConfigurationExtensions.cs @@ -6,11 +6,12 @@ namespace NServiceBus public static class HostInfoConfigurationExtensions { /// - /// Entry point for HostInfo related configuration + /// Entry point for HostInfo related configuration. /// - /// instance. - public static HostInfoSettings UniquelyIdentifyRunningInstance(this BusConfiguration config) + /// The instance to apply the settings to. + public static HostInfoSettings UniquelyIdentifyRunningInstance(this EndpointConfiguration config) { + Guard.AgainstNull(nameof(config), config); return new HostInfoSettings(config); } } diff --git a/src/NServiceBus.Core/Hosting/HostInfoSettings.cs b/src/NServiceBus.Core/Hosting/HostInfoSettings.cs index ec8028db9bd..1ab698ca738 100644 --- a/src/NServiceBus.Core/Hosting/HostInfoSettings.cs +++ b/src/NServiceBus.Core/Hosting/HostInfoSettings.cs @@ -1,17 +1,15 @@ namespace NServiceBus { using System; - using NServiceBus.Hosting; - using NServiceBus.Utils; + using Features; + using Hosting; /// - /// Configuration class for settings. + /// Configuration class for settings. /// public class HostInfoSettings { - BusConfiguration config; - - internal HostInfoSettings(BusConfiguration config) + internal HostInfoSettings(EndpointConfiguration config) { this.config = config; } @@ -21,7 +19,8 @@ internal HostInfoSettings(BusConfiguration config) /// /// /// This mode is only recommended if upgrades are deployed always to the same path. - /// When using Octopus Deploy do not use this mode, instead use . + /// When using Octopus Deploy do not use this mode, instead use + /// . /// public HostInfoSettings UsingInstalledFilePath() { @@ -39,20 +38,35 @@ public HostInfoSettings UsingInstalledFilePath() /// public HostInfoSettings UsingCustomIdentifier(Guid id) { - config.Settings.Set("NServiceBus.HostInformation.HostId", id); + config.Settings.Set(HostInformationFeature.HostIdSettingsKey, id); return this; } /// - /// In this mode, a host id will be generated from and . + /// In this mode, a host id will be generated from and . /// /// - /// This mode is recommended when deploying in Azure roles or is not appropriate. + /// This mode is recommended when deploying in Azure roles or is not appropriate. /// public HostInfoSettings UsingNames(string instanceName, string hostName) { - config.Settings.Set("NServiceBus.HostInformation.HostId", DeterministicGuid.Create(instanceName, hostName)); + Guard.AgainstNullAndEmpty(nameof(instanceName), instanceName); + Guard.AgainstNullAndEmpty(nameof(hostName), hostName); + + config.Settings.Set(HostInformationFeature.HostIdSettingsKey, DeterministicGuid.Create(instanceName, hostName)); return this; } + + /// + /// Allows to override the display name. + /// + public HostInfoSettings UsingCustomDisplayName(string displayName) + { + Guard.AgainstNullAndEmpty(nameof(displayName), displayName); + config.Settings.Set("NServiceBus.HostInformation.DisplayName", displayName); + return this; + } + + EndpointConfiguration config; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Hosting/HostInformation.cs b/src/NServiceBus.Core/Hosting/HostInformation.cs index ae3df000187..36909ebc37c 100644 --- a/src/NServiceBus.Core/Hosting/HostInformation.cs +++ b/src/NServiceBus.Core/Hosting/HostInformation.cs @@ -3,32 +3,34 @@ namespace NServiceBus.Hosting using System; using System.Collections.Generic; using System.Diagnostics; + using Support; /// - /// Provides information about the process hosting this endpoint + /// Provides information about the process hosting this endpoint. /// public class HostInformation { /// - /// Creates a new instance + /// Creates a new instance. /// - /// The id of the host - /// The display name of the host + /// The id of the host. + /// The display name of the host. public HostInformation(Guid hostId, string displayName) : this(hostId, displayName, new Dictionary { - {"Machine", Environment.MachineName}, + {"Machine", RuntimeEnvironment.MachineName}, {"ProcessID", Process.GetCurrentProcess().Id.ToString()}, - {"UserName", Environment.UserName}, + {"UserName", Environment.UserName} }) - { } + { + } /// - /// Creates a new instance + /// Creates a new instance. /// - /// The id of the host - /// The display name of the host - /// A set of properties for the host. This might vary from host to host + /// The id of the host. + /// The display name of the host. + /// A set of properties for the host. This might vary from host to host. public HostInformation(Guid hostId, string displayName, Dictionary properties) { HostId = hostId; @@ -37,17 +39,17 @@ public HostInformation(Guid hostId, string displayName, Dictionary - /// The unique identifier for this host + /// The unique identifier for this host. /// public Guid HostId { get; private set; } /// - /// The display name of this host + /// The display name of this host. /// public string DisplayName { get; private set; } - + /// - /// A set of properties for the host. This might vary from host to host + /// A set of properties for the host. This might vary from host to host. /// public Dictionary Properties { get; private set; } } diff --git a/src/NServiceBus.Core/Hosting/HostInformationFeature.cs b/src/NServiceBus.Core/Hosting/HostInformationFeature.cs new file mode 100644 index 00000000000..b7b2367d600 --- /dev/null +++ b/src/NServiceBus.Core/Hosting/HostInformationFeature.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.Features +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Hosting; + using Support; + + class HostInformationFeature : Feature + { + public HostInformationFeature() + { + EnableByDefault(); + Defaults(s => + { + var fullPathToStartingExe = PathUtilities.SanitizedPath(Environment.CommandLine); + + if (!s.HasExplicitValue(HostIdSettingsKey)) + { + s.SetDefault(HostIdSettingsKey, DeterministicGuid.Create(fullPathToStartingExe, RuntimeEnvironment.MachineName)); + } + s.SetDefault("NServiceBus.HostInformation.DisplayName", RuntimeEnvironment.MachineName); + s.SetDefault("NServiceBus.HostInformation.Properties", new Dictionary + { + {"Machine", RuntimeEnvironment.MachineName}, + {"ProcessID", Process.GetCurrentProcess().Id.ToString()}, + {"UserName", Environment.UserName}, + {"PathToExecutable", fullPathToStartingExe} + }); + }); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var hostInformation = new HostInformation(context.Settings.Get(HostIdSettingsKey), + context.Settings.Get("NServiceBus.HostInformation.DisplayName"), + context.Settings.Get>("NServiceBus.HostInformation.Properties")); + + context.Container.ConfigureComponent(() => hostInformation, DependencyLifecycle.SingleInstance); + + var endpointName = context.Settings.EndpointName(); + context.Pipeline.Register("AuditHostInformation", new AuditHostInformationBehavior(hostInformation, endpointName), "Adds audit host information"); + context.Pipeline.Register("AddHostInfoHeaders", new AddHostInfoHeadersBehavior(hostInformation, endpointName), "Adds host info headers to outgoing headers"); + } + + internal const string HostIdSettingsKey = "NServiceBus.HostInformation.HostId"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IAuthorizeSubscriptions.cs b/src/NServiceBus.Core/IAuthorizeSubscriptions.cs deleted file mode 100644 index b8793d694d3..00000000000 --- a/src/NServiceBus.Core/IAuthorizeSubscriptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System.Collections.Generic; - using System.Security.Principal; - - /// - /// Implementer will be called by the infrastructure in order to authorize - /// subscribe and unsubscribe requests from other endpoints. - /// - /// Infrastructure automatically registers one implementing type in the container as a singleton. - /// - public interface IAuthorizeSubscriptions - { - /// - /// Return true if the client endpoint is to be allowed to subscribe to the given message type. - /// Implementors can access the impersonated user via - /// - bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers); - - /// - /// Return true if the client endpoint is to be allowed to unsubscribe to the given message type. - /// - bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers); - } -} diff --git a/src/NServiceBus.Core/IBus.cs b/src/NServiceBus.Core/IBus.cs deleted file mode 100644 index 8f0a964f188..00000000000 --- a/src/NServiceBus.Core/IBus.cs +++ /dev/null @@ -1,108 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Defines a bus to be used with NServiceBus. - /// - public interface IBus : ISendOnlyBus - { - /// - /// Subscribes to receive published messages of the specified type. - /// This method is only necessary if you turned off auto-subscribe. - /// - /// The type of message to subscribe to. - void Subscribe(Type messageType); - - /// - /// Subscribes to receive published messages of type T. - /// This method is only necessary if you turned off auto-subscribe. - /// - /// The type of message to subscribe to. - void Subscribe(); - - /// - /// Unsubscribes from receiving published messages of the specified type. - /// - /// The type of message to subscribe to. - void Unsubscribe(Type messageType); - - /// - /// Unsubscribes from receiving published messages of the specified type. - /// - /// The type of message to unsubscribe from. - void Unsubscribe(); - - /// - /// Sends the message back to the current bus. - /// - /// The message to send. - ICallback SendLocal(object message); - - /// - /// Instantiates a message of type T and sends it back to the current bus. - /// - /// The type of message, usually an interface. - /// An action which initializes properties of the message - ICallback SendLocal(Action messageConstructor); - - /// - /// Defers the processing of the message for the given delay. This feature is using the timeout manager so make sure that you enable timeouts - /// - ICallback Defer(TimeSpan delay, object message); - - /// - /// Defers the processing of the message until the specified time. This feature is using the timeout manager so make sure that you enable timeouts - /// - ICallback Defer(DateTime processAt, object message); - - /// - /// Sends the message to the endpoint which sent the message currently being handled on this thread. - /// - /// The message to send. - void Reply(object message); - - /// - /// Instantiates a message of type T and performs a regular . - /// - /// The type of message, usually an interface - /// An action which initializes properties of the message - void Reply(Action messageConstructor); - - /// - /// Returns a completion message with the specified error code to the sender - /// of the message being handled. The type T can only be an enum or an integer. - /// - void Return(T errorEnum); - - /// - /// Moves the message being handled to the back of the list of available - /// messages so it can be handled later. - /// - void HandleCurrentMessageLater(); - - /// - /// Forwards the current message being handled to the destination maintaining - /// all of its transport-level properties and headers. - /// - void ForwardCurrentMessageTo(string destination); - - /// - /// Tells the bus to stop dispatching the current message to additional - /// handlers. - /// - void DoNotContinueDispatchingCurrentMessageToHandlers(); - - /// - /// Gets the message context containing the Id, return address, and headers - /// of the message currently being handled on this thread. - /// - IMessageContext CurrentMessageContext { get; } - - /// - /// Support for in-memory operations. - /// - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5.5", Message = "Removed to reduce complexity and API confusion.")] - IInMemoryOperations InMemory { get; } - } -} diff --git a/src/NServiceBus.Core/IBus_Obsoletes.cs b/src/NServiceBus.Core/IBus_Obsoletes.cs deleted file mode 100644 index efc82c4c7c4..00000000000 --- a/src/NServiceBus.Core/IBus_Obsoletes.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Obsoleted IBus methods - /// - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "Placeholder for obsoletes")] - public static class IBus_Obsoletes - { - // ReSharper disable UnusedParameter.Global - - /// - /// Creates an instance of the message type T. - /// - /// The type of message interface to instantiate. - /// A message object that implements the interface T. - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "Since multi message sends is obsoleted in v5 use `IBus.Send()` instead")] - public static T CreateInstance(this IBus bus) - { - throw new NotImplementedException(); - } - - /// - /// Creates an instance of the message type T and fills it with data. - /// - /// The type of message interface to instantiate. - /// The bus - /// An action to set various properties of the instantiated object. - /// A message object that implements the interface T. - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "Since multi message sends is obsoleted in v5 use `IBus.Send()` instead")] - public static T CreateInstance(this IBus bus, Action action) - { - throw new NotImplementedException(); - } - - /// - /// Creates an instance of the given message type. - /// - /// The bus - /// The type of message to instantiate. - /// A message object that implements the given interface. - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "Since multi message sends is obsoleted in v5 use `IBus.Send()` instead")] - public static object CreateInstance(this IBus bus, Type messageType) - { - throw new NotImplementedException(); - } - - // ReSharper restore UnusedParameter.Global - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/ICallback.cs b/src/NServiceBus.Core/ICallback.cs deleted file mode 100644 index 3125f94ea99..00000000000 --- a/src/NServiceBus.Core/ICallback.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Threading.Tasks; - - /// - /// Objects of this interface are returned from calling IBus.Send. - /// The interface allows the caller to register for a callback when a response - /// is received to their original call to . - /// - public interface ICallback - { - /// - /// Registers a callback to be invoked when a response arrives to the message sent. - /// The return code is returned as an int - /// - Task Register(); - - /// - /// Registers a callback to be invoked when a response arrives to the message sent. - /// The return code is cast to the given enumerated type - T. - /// - /// An enumeration type or an integer. - Task Register(); - - /// - /// Registers a function to be invoked when a response arrives to the message sent. - /// Returns a Task that can be used with async/await operations. - /// - /// A function to call upon completion that returns a value of type T. - /// The type of the value to be returned from the function. - /// A Task that can be used with async/await operations. - Task Register(Func completion); - - /// - /// Registers an action to be invoked when a response arrives to the message sent. - /// Returns a Task that can be used with async/await operations. - /// - /// An action to call upon completion that does not return a value. - /// A Task that can be used with async/await operations. - Task Register(Action completion); - - /// - /// Registers a callback to be invoked when a response arrives to the message sent. - /// - /// The callback to invoke. - /// State that will be passed to the callback method. - /// An IAsyncResult useful for integration with ASP.NET async tasks. - IAsyncResult Register(AsyncCallback callback, object state); - - /// - /// Registers a callback to be invoked when a response arrives to the message sent. - /// The return code is cast to the given enumerated type - T. - /// - /// An enumeration type or an integer. - void Register(Action callback); - - /// - /// Registers a callback to be invoked when a response arrives to the message sent. - /// The return code is cast to the given enumerated type - T. - /// Pass either a System.Web.UI.Page or a System.Web.Mvc.AsyncController as the synchronizer. - /// - void Register(Action callback, object synchronizer); - } -} diff --git a/src/NServiceBus.Core/ICommand.cs b/src/NServiceBus.Core/ICommand.cs index ca7392cf6b8..893545427bb 100644 --- a/src/NServiceBus.Core/ICommand.cs +++ b/src/NServiceBus.Core/ICommand.cs @@ -1,7 +1,9 @@ namespace NServiceBus { /// - /// Marker interface to indicate that a class is a command message + /// Marker interface to indicate that a class is a command message. /// - public interface ICommand : IMessage{} + public interface ICommand : IMessage + { + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/IEndpointInstance.cs b/src/NServiceBus.Core/IEndpointInstance.cs new file mode 100644 index 00000000000..2e1d7295730 --- /dev/null +++ b/src/NServiceBus.Core/IEndpointInstance.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + + /// + /// Represents an endpoint in the running phase. + /// + public interface IEndpointInstance : IMessageSession + { + /// + /// Stops the endpoint. + /// + Task Stop(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IEvent.cs b/src/NServiceBus.Core/IEvent.cs index 5ba8f46a924..a91c801b90f 100644 --- a/src/NServiceBus.Core/IEvent.cs +++ b/src/NServiceBus.Core/IEvent.cs @@ -1,7 +1,9 @@ namespace NServiceBus { /// - /// Marker interface to indicate that a class is a event message + /// Marker interface to indicate that a class is a event message. /// - public interface IEvent : IMessage { } + public interface IEvent : IMessage + { + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/IExcludesBuilder.cs b/src/NServiceBus.Core/IExcludesBuilder.cs deleted file mode 100644 index b8f09ec0096..00000000000 --- a/src/NServiceBus.Core/IExcludesBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus -{ - using System.Collections.Generic; - using System.Reflection; - - /// - /// Supporting the fluent interface in - /// - public interface IExcludesBuilder : IEnumerable - { - /// - /// Indicate that the given assembly expression should also be excluded. - /// You can call this method multiple times. - /// - IExcludesBuilder And(string assemblyExpression); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IHandleMessages.cs b/src/NServiceBus.Core/IHandleMessages.cs new file mode 100644 index 00000000000..c8cc604543b --- /dev/null +++ b/src/NServiceBus.Core/IHandleMessages.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Defines a message handler. + /// + /// The type of message to be handled. + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IHandleMessages + { + /// + /// Handles a message. + /// + /// The message to handle. + /// The context of the currently handled message. + /// + /// This method will be called when a message arrives on at the endpoint and should contain + /// the custom logic to execute when the message is received. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task Handle(T message, IMessageHandlerContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IInMemoryOperations.cs b/src/NServiceBus.Core/IInMemoryOperations.cs deleted file mode 100644 index 6f9d7bceaf4..00000000000 --- a/src/NServiceBus.Core/IInMemoryOperations.cs +++ /dev/null @@ -1,8 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus -{ - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5.5", Message = "Removed to reduce complexity and API confusion.")] - public interface IInMemoryOperations - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IIncludesBuilder.cs b/src/NServiceBus.Core/IIncludesBuilder.cs deleted file mode 100644 index 655dddc918d..00000000000 --- a/src/NServiceBus.Core/IIncludesBuilder.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus -{ - using System.Collections.Generic; - using System.Reflection; - - /// - /// Supporting the fluent interface in - /// - public interface IIncludesBuilder : IEnumerable - { - /// - /// Indicate that assemblies matching the given expression should also be included. - /// You can call this method multiple times. - /// - IIncludesBuilder And(string assemblyExpression); - - /// - /// Indicate that assemblies matching the given expression should be excluded. - /// Use the 'And' method to indicate other assemblies to be skipped. - /// - IExcludesBuilder Except(string assemblyExpression); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IManageMessageHeaders.cs b/src/NServiceBus.Core/IManageMessageHeaders.cs deleted file mode 100644 index fa257af69cf..00000000000 --- a/src/NServiceBus.Core/IManageMessageHeaders.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// implementers should also implement this interface to support get/set of headers for current message. - /// - public interface IManageMessageHeaders - { - /// - /// The used to set the header in the bus.SetMessageHeader(msg, key, value) method. - /// - Action SetHeaderAction { get; } - /// - /// The used to get the header value in the bus.GetMessageHeader(msg, key) method. - /// - Func GetHeaderAction { get; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessage.cs b/src/NServiceBus.Core/IMessage.cs index f45155eb921..3fb3b4017e5 100644 --- a/src/NServiceBus.Core/IMessage.cs +++ b/src/NServiceBus.Core/IMessage.cs @@ -1,10 +1,13 @@ namespace NServiceBus { + using JetBrains.Annotations; + /// - /// Marker interface to indicate that a class is a message suitable - /// for transmission and handling by an NServiceBus. - /// + /// Marker interface to indicate that a class is a message suitable + /// for transmission and handling by an NServiceBus. + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public interface IMessage { } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageContext.cs b/src/NServiceBus.Core/IMessageContext.cs deleted file mode 100644 index 5c7edad94f7..00000000000 --- a/src/NServiceBus.Core/IMessageContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus -{ - using System.Collections.Generic; - - /// - /// Contains out-of-band information on the logical message. - /// - public interface IMessageContext - { - /// - /// Returns the Id of the message. - /// - string Id { get; } - - /// - /// The address of the endpoint that sent the current message being handled. - /// - Address ReplyToAddress { get; } - - /// - /// Gets the list of key/value pairs found in the header of the message. - /// - IDictionary Headers { get; } - - } -} diff --git a/src/NServiceBus.Core/IMessageCreator.cs b/src/NServiceBus.Core/IMessageCreator.cs index 87a2fde8ea8..c450322347b 100644 --- a/src/NServiceBus.Core/IMessageCreator.cs +++ b/src/NServiceBus.Core/IMessageCreator.cs @@ -29,4 +29,4 @@ public interface IMessageCreator /// A message object that implements the given interface. object CreateInstance(Type messageType); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageHandler.cs b/src/NServiceBus.Core/IMessageHandler.cs deleted file mode 100644 index 0e5f8f2816c..00000000000 --- a/src/NServiceBus.Core/IMessageHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus -{ - /// - /// Defines a message handler. - /// - /// The type of message to be handled. - public interface IHandleMessages - { - /// - /// Handles a message. - /// - /// The message to handle. - /// - /// This method will be called when a message arrives on the bus and should contain - /// the custom logic to execute when the message is received. - void Handle(T message); - } -} diff --git a/src/NServiceBus.Core/IMessageHandlerContext.cs b/src/NServiceBus.Core/IMessageHandlerContext.cs new file mode 100644 index 00000000000..8ab7c127734 --- /dev/null +++ b/src/NServiceBus.Core/IMessageHandlerContext.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Persistence; + + /// + /// The context of the currently processed message for a message handler. + /// + public interface IMessageHandlerContext : IMessageProcessingContext + { + /// + /// Gets the synchronized storage session for processing the current message. NServiceBus makes sure the changes made + /// via this session will be persisted before the message receive is acknowledged. + /// + SynchronizedStorageSession SynchronizedStorageSession { get; } + + /// + /// Moves the message being handled to the back of the list of available + /// messages so it can be handled later. + /// + Task HandleCurrentMessageLater(); + + /// + /// Tells the endpoint to stop dispatching the current message to additional + /// handlers. + /// + void DoNotContinueDispatchingCurrentMessageToHandlers(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageProcessingContext.cs b/src/NServiceBus.Core/IMessageProcessingContext.cs new file mode 100644 index 00000000000..c20ba9e12ad --- /dev/null +++ b/src/NServiceBus.Core/IMessageProcessingContext.cs @@ -0,0 +1,48 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// The context of the currently processed message within the processing pipeline. + /// + public interface IMessageProcessingContext : IPipelineContext + { + /// + /// The Id of the currently processed message. + /// + string MessageId { get; } + + /// + /// The address of the endpoint that sent the current message being handled. + /// + string ReplyToAddress { get; } + + /// + /// Gets the list of key/value pairs found in the header of the message. + /// + IReadOnlyDictionary MessageHeaders { get; } + + /// + /// Sends the message to the endpoint which sent the message currently being handled. + /// + /// The message to send. + /// Options for this reply. + Task Reply(object message, ReplyOptions options); + + /// + /// Instantiates a message of type T and performs a regular . + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// Options for this reply. + Task Reply(Action messageConstructor, ReplyOptions options); + + /// + /// Forwards the current message being handled to the destination maintaining + /// all of its transport-level properties and headers. + /// + Task ForwardCurrentMessageTo(string destination); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageProcessingContextExtensions.cs b/src/NServiceBus.Core/IMessageProcessingContextExtensions.cs new file mode 100644 index 00000000000..64f4d44745e --- /dev/null +++ b/src/NServiceBus.Core/IMessageProcessingContextExtensions.cs @@ -0,0 +1,38 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// Syntactic sugar for . + /// + public static class IMessageProcessingContextExtensions + { + /// + /// Sends the message to the endpoint which sent the message currently being handled on this thread. + /// + /// Object being extended. + /// The message to send. + public static Task Reply(this IMessageProcessingContext context, object message) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(message), message); + + return context.Reply(message, new ReplyOptions()); + } + + /// + /// Instantiates a message of type T and performs a regular Reply. + /// + /// The type of message, usually an interface. + /// Object being extended. + /// An action which initializes properties of the message. + public static Task Reply(this IMessageProcessingContext context, Action messageConstructor) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + return context.Reply(messageConstructor, new ReplyOptions()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageSession.cs b/src/NServiceBus.Core/IMessageSession.cs new file mode 100644 index 00000000000..0be01210322 --- /dev/null +++ b/src/NServiceBus.Core/IMessageSession.cs @@ -0,0 +1,56 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// A session which provides basic message operations. + /// + public interface IMessageSession + { + /// + /// Sends the provided message. + /// + /// The message to send. + /// The options for the send. + Task Send(object message, SendOptions options); + + /// + /// Instantiates a message of type T and sends it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// The options for the send. + Task Send(Action messageConstructor, SendOptions options); + + /// + /// Publish the message to subscribers. + /// + /// The message to publish. + /// The options for the publish. + Task Publish(object message, PublishOptions options); + + /// + /// Instantiates a message of type T and publishes it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// Specific options for this event. + Task Publish(Action messageConstructor, PublishOptions publishOptions); + + /// + /// Subscribes to receive published messages of the specified type. + /// This method is only necessary if you turned off auto-subscribe. + /// + /// The type of event to subscribe to. + /// Options for the subscribe. + Task Subscribe(Type eventType, SubscribeOptions options); + + /// + /// Unsubscribes to receive published messages of the specified type. + /// + /// The type of event to unsubscribe to. + /// Options for the subscribe. + Task Unsubscribe(Type eventType, UnsubscribeOptions options); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IMessageSessionExtensions.cs b/src/NServiceBus.Core/IMessageSessionExtensions.cs new file mode 100644 index 00000000000..c50d11dd4ec --- /dev/null +++ b/src/NServiceBus.Core/IMessageSessionExtensions.cs @@ -0,0 +1,206 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// Syntactic sugar for . + /// + public static class IMessageSessionExtensions + { + /// + /// Sends the provided message. + /// + /// The instance of to use for the action. + /// The message to send. + public static Task Send(this IMessageSession session, object message) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(message), message); + + return session.Send(message, new SendOptions()); + } + + /// + /// Instantiates a message of and sends it. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// An action which initializes properties of the message. + /// + /// The message will be sent to the destination configured for . + /// + public static Task Send(this IMessageSession session, Action messageConstructor) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + return session.Send(messageConstructor, new SendOptions()); + } + + /// + /// Sends the message. + /// + /// The instance of to use for the action. + /// The address of the destination to which the message will be sent. + /// The message to send. + public static Task Send(this IMessageSession session, string destination, object message) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + Guard.AgainstNull(nameof(message), message); + + var options = new SendOptions(); + + options.SetDestination(destination); + + return session.Send(message, options); + } + + /// + /// Instantiates a message of type T and sends it to the given destination. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// The destination to which the message will be sent. + /// An action which initializes properties of the message. + public static Task Send(this IMessageSession session, string destination, Action messageConstructor) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + var options = new SendOptions(); + + options.SetDestination(destination); + + return session.Send(messageConstructor, options); + } + + /// + /// Sends the message back to the current endpoint. + /// + /// Object being extended. + /// The message to send. + public static Task SendLocal(this IMessageSession session, object message) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(message), message); + + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + return session.Send(message, options); + } + + /// + /// Instantiates a message of type T and sends it back to the current endpoint. + /// + /// The type of message, usually an interface. + /// Object being extended. + /// An action which initializes properties of the message. + public static Task SendLocal(this IMessageSession session, Action messageConstructor) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + return session.Send(messageConstructor, options); + } + + /// + /// Publish the message to subscribers. + /// + /// The instance of to use for the action. + /// The message to publish. + public static Task Publish(this IMessageSession session, object message) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(message), message); + + return session.Publish(message, new PublishOptions()); + } + + /// + /// Publish the message to subscribers. + /// + /// The instance of to use for the action. + /// The message type. + public static Task Publish(this IMessageSession session) + { + Guard.AgainstNull(nameof(session), session); + + return session.Publish(_ => { }, new PublishOptions()); + } + + /// + /// Instantiates a message of type T and publishes it. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// An action which initializes properties of the message. + public static Task Publish(this IMessageSession session, Action messageConstructor) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + return session.Publish(messageConstructor, new PublishOptions()); + } + + /// + /// Subscribes to receive published messages of the specified type. + /// This method is only necessary if you turned off auto-subscribe. + /// + /// Object being extended. + /// The type of message to subscribe to. + public static Task Subscribe(this IMessageSession session, Type messageType) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(messageType), messageType); + + return session.Subscribe(messageType, new SubscribeOptions()); + } + + /// + /// Subscribes to receive published messages of type T. + /// This method is only necessary if you turned off auto-subscribe. + /// + /// Object being extended. + /// The type of message to subscribe to. + public static Task Subscribe(this IMessageSession session) + { + Guard.AgainstNull(nameof(session), session); + + return session.Subscribe(typeof(T), new SubscribeOptions()); + } + + /// + /// Unsubscribes from receiving published messages of the specified type. + /// + /// Object being extended. + /// The type of message to subscribe to. + public static Task Unsubscribe(this IMessageSession session, Type messageType) + { + Guard.AgainstNull(nameof(session), session); + Guard.AgainstNull(nameof(messageType), messageType); + + return session.Unsubscribe(messageType, new UnsubscribeOptions()); + } + + /// + /// Unsubscribes from receiving published messages of the specified type. + /// + /// Object being extended. + /// The type of message to unsubscribe from. + public static Task Unsubscribe(this IMessageSession session) + { + Guard.AgainstNull(nameof(session), session); + + return session.Unsubscribe(typeof(T), new UnsubscribeOptions()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/INeedInitialization.cs b/src/NServiceBus.Core/INeedInitialization.cs index ced24e23b50..8feade0f116 100644 --- a/src/NServiceBus.Core/INeedInitialization.cs +++ b/src/NServiceBus.Core/INeedInitialization.cs @@ -1,14 +1,17 @@ namespace NServiceBus { + using JetBrains.Annotations; + /// /// Indicate that the implementing class will specify configuration. /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public interface INeedInitialization { /// /// Allows to override default settings. /// /// Endpoint configuration builder. - void Customize(BusConfiguration configuration); + void Customize(EndpointConfiguration configuration); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/IPipelineContext.cs b/src/NServiceBus.Core/IPipelineContext.cs new file mode 100644 index 00000000000..6ec214db36a --- /dev/null +++ b/src/NServiceBus.Core/IPipelineContext.cs @@ -0,0 +1,42 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Extensibility; + + /// + /// The context for the current message handling pipeline. + /// + public interface IPipelineContext : IExtendable + { + /// + /// Sends the provided message. + /// + /// The message to send. + /// The options for the send. + Task Send(object message, SendOptions options); + + /// + /// Instantiates a message of type T and sends it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// The options for the send. + Task Send(Action messageConstructor, SendOptions options); + + /// + /// Publish the message to subscribers. + /// + /// The message to publish. + /// The options for the publish. + Task Publish(object message, PublishOptions options); + + /// + /// Instantiates a message of type T and publishes it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// Specific options for this event. + Task Publish(Action messageConstructor, PublishOptions publishOptions); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IPipelineContextExtensions.cs b/src/NServiceBus.Core/IPipelineContextExtensions.cs new file mode 100644 index 00000000000..c0a18f142d5 --- /dev/null +++ b/src/NServiceBus.Core/IPipelineContextExtensions.cs @@ -0,0 +1,154 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + /// + /// Syntactic sugar for . + /// + public static class IPipelineContextExtensions + { + /// + /// Sends the provided message. + /// + /// The instance of to use for the action. + /// The message to send. + public static Task Send(this IPipelineContext context, object message) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(message), message); + + return context.Send(message, new SendOptions()); + } + + /// + /// Instantiates a message of and sends it. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// An action which initializes properties of the message. + /// + /// The message will be sent to the destination configured for . + /// + public static Task Send(this IPipelineContext context, Action messageConstructor) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + return context.Send(messageConstructor, new SendOptions()); + } + + /// + /// Sends the message. + /// + /// The instance of to use for the action. + /// The address of the destination to which the message will be sent. + /// The message to send. + public static Task Send(this IPipelineContext context, string destination, object message) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + Guard.AgainstNull(nameof(message), message); + + var options = new SendOptions(); + + options.SetDestination(destination); + + return context.Send(message, options); + } + + /// + /// Instantiates a message of type T and sends it to the given destination. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// The destination to which the message will be sent. + /// An action which initializes properties of the message. + public static Task Send(this IPipelineContext context, string destination, Action messageConstructor) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + var options = new SendOptions(); + + options.SetDestination(destination); + + return context.Send(messageConstructor, options); + } + + /// + /// Sends the message back to the current endpoint. + /// + /// Object being extended. + /// The message to send. + public static Task SendLocal(this IPipelineContext context, object message) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(message), message); + + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + return context.Send(message, options); + } + + /// + /// Instantiates a message of type T and sends it back to the current endpoint. + /// + /// The type of message, usually an interface. + /// Object being extended. + /// An action which initializes properties of the message. + public static Task SendLocal(this IPipelineContext context, Action messageConstructor) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + var options = new SendOptions(); + + options.RouteToThisEndpoint(); + + return context.Send(messageConstructor, options); + } + + /// + /// Publish the message to subscribers. + /// + /// The instance of to use for the action. + /// The message to publish. + public static Task Publish(this IPipelineContext context, object message) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(message), message); + + return context.Publish(message, new PublishOptions()); + } + + /// + /// Publish the message to subscribers. + /// + /// The instance of to use for the action. + /// The message type. + public static Task Publish(this IPipelineContext context) + { + Guard.AgainstNull(nameof(context), context); + + return context.Publish(_ => { }, new PublishOptions()); + } + + /// + /// Instantiates a message of type T and publishes it. + /// + /// The type of message, usually an interface. + /// The instance of to use for the action. + /// An action which initializes properties of the message. + public static Task Publish(this IPipelineContext context, Action messageConstructor) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(messageConstructor), messageConstructor); + + return context.Publish(messageConstructor, new PublishOptions()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ISendOnlyBus.cs b/src/NServiceBus.Core/ISendOnlyBus.cs deleted file mode 100644 index 35a4763f1d4..00000000000 --- a/src/NServiceBus.Core/ISendOnlyBus.cs +++ /dev/null @@ -1,112 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - - /// - /// Provides the subset of bus operations that is applicable for a send only bus - /// - public interface ISendOnlyBus: IDisposable - { - /// - /// Publish the message to subscribers. - /// - void Publish(T message); - - /// - /// Publish the message to subscribers. - /// - void Publish(); - - /// - /// Instantiates a message of type T and publishes it. - /// - /// The type of message, usually an interface - /// An action which initializes properties of the message - void Publish(Action messageConstructor); - - /// - /// Sends the provided message. - /// - /// The message to send. - ICallback Send(object message); - - /// - /// Instantiates a message of type T and sends it. - /// - /// The type of message, usually an interface - /// An action which initializes properties of the message - /// - /// The message will be sent to the destination configured for T - /// - ICallback Send(Action messageConstructor); - - /// - /// Sends the message. - /// - /// - /// The address of the destination to which the message will be sent. - /// - /// The message to send. - ICallback Send(string destination, object message); - - /// - /// Sends the provided message. - /// - /// - /// The address to which the message will be sent. - /// - /// The message to send. - ICallback Send(Address address, object message); - - /// - /// Instantiates a message of type T and sends it to the given destination. - /// - /// The type of message, usually an interface - /// The destination to which the message will be sent. - /// An action which initializes properties of the message - ICallback Send(string destination, Action messageConstructor); - - /// - /// Instantiates a message of type T and sends it to the given address. - /// - /// The type of message, usually an interface - /// The address to which the message will be sent. - /// An action which initializes properties of the message - ICallback Send(Address address, Action messageConstructor); - - /// - /// Sends the message to the destination as well as identifying this - /// as a response to a message containing the Id found in correlationId. - /// - ICallback Send(string destination, string correlationId, object message); - - /// - /// Sends the message to the given address as well as identifying this - /// as a response to a message containing the Id found in correlationId. - /// - ICallback Send(Address address, string correlationId, object message); - - /// - /// Instantiates a message of the type T using the given messageConstructor, - /// and sends it to the destination identifying it as a response to a message - /// containing the Id found in correlationId. - /// - ICallback Send(string destination, string correlationId, Action messageConstructor); - - /// - /// Instantiates a message of the type T using the given messageConstructor, - /// and sends it to the given address identifying it as a response to a message - /// containing the Id found in correlationId. - /// - ICallback Send(Address address, string correlationId, Action messageConstructor); - - /// - /// Gets the list of key/value pairs that will be in the header of - /// messages being sent by the same thread. - /// - /// This value will be cleared when a thread receives a message. - /// - IDictionary OutgoingHeaders { get; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IStartableBus.cs b/src/NServiceBus.Core/IStartableBus.cs deleted file mode 100644 index b8b07755e1f..00000000000 --- a/src/NServiceBus.Core/IStartableBus.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus -{ - /// - /// The interface used for starting and stopping an IBus. - /// - public interface IStartableBus : IBus - { - /// - /// Starts the bus and returns a reference to it. - /// - /// A reference to the bus. - IBus Start(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/IStartableEndpoint.cs b/src/NServiceBus.Core/IStartableEndpoint.cs new file mode 100644 index 00000000000..29d98bf414a --- /dev/null +++ b/src/NServiceBus.Core/IStartableEndpoint.cs @@ -0,0 +1,16 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + + /// + /// Represents an endpoint in the start-up phase. + /// + public interface IStartableEndpoint + { + /// + /// Starts the endpoint and returns a reference to it. + /// + /// A reference to the endpoint. + Task Start(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/IWantToRunBeforeConfiguration.cs b/src/NServiceBus.Core/IWantToRunBeforeConfiguration.cs deleted file mode 100644 index b3b8d0b4710..00000000000 --- a/src/NServiceBus.Core/IWantToRunBeforeConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus -{ - /// - /// Indicates that this class contains logic that need to be executed before other configuration - /// - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "`IWantToRunBeforeConfiguration` is no longer in use. Please use the Feature concept instead and register a Default() in the ctor of your feature. If you used this to apply your own conventions please use use `configuration.Conventions().Defining...` , where configuration is an instance of type `BusConfiguration` available by implementing `IConfigureThisEndpoint` or `INeedInitialization`.")] - public interface IWantToRunBeforeConfiguration - { - /// - /// Invoked before configuration starts - /// -// ReSharper disable once UnusedParameter.Global - void Init(Configure configure); - } -} diff --git a/src/NServiceBus.Core/IWantToRunBeforeConfigurationIsFinalized.cs b/src/NServiceBus.Core/IWantToRunBeforeConfigurationIsFinalized.cs index cc450c50cb1..30658468ffd 100644 --- a/src/NServiceBus.Core/IWantToRunBeforeConfigurationIsFinalized.cs +++ b/src/NServiceBus.Core/IWantToRunBeforeConfigurationIsFinalized.cs @@ -1,14 +1,18 @@ namespace NServiceBus { + using JetBrains.Annotations; + using Settings; + /// /// Indicates that this class contains logic that needs to run just before - /// configuration is finalized + /// configuration is finalized. /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public interface IWantToRunBeforeConfigurationIsFinalized { /// - /// Invoked before configuration is finalized and locked + /// Invoked before configuration is finalized and locked. /// - void Run(Configure config); + void Run(SettingsHolder settings); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/IWantToRunWhenBusStartsAndStops.cs b/src/NServiceBus.Core/IWantToRunWhenBusStartsAndStops.cs deleted file mode 100644 index 625668124c7..00000000000 --- a/src/NServiceBus.Core/IWantToRunWhenBusStartsAndStops.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus -{ - /// - /// Implementers will be invoked when the endpoint starts up. - /// Dependency injection is provided for these types. - /// - public interface IWantToRunWhenBusStartsAndStops - { - /// - /// Method called at startup. - /// - void Start(); - - /// - /// Method called on shutdown. - /// - void Stop(); - } -} diff --git a/src/NServiceBus.Core/IdGeneration/CombGuid.cs b/src/NServiceBus.Core/IdGeneration/CombGuid.cs index f73f0651921..19d50990611 100644 --- a/src/NServiceBus.Core/IdGeneration/CombGuid.cs +++ b/src/NServiceBus.Core/IdGeneration/CombGuid.cs @@ -7,7 +7,7 @@ namespace NServiceBus static class CombGuid { /// - /// Generate a new using the comb algorithm. + /// Generate a new using the comb algorithm. /// public static Guid Generate() { @@ -23,7 +23,7 @@ public static Guid Generate() // Convert to a byte array // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 var daysArray = BitConverter.GetBytes(days.Days); - var millisecondArray = BitConverter.GetBytes((long)(timeOfDay.TotalMilliseconds / 3.333333)); + var millisecondArray = BitConverter.GetBytes((long) (timeOfDay.TotalMilliseconds/3.333333)); // Reverse the bytes to match SQL Servers ordering Array.Reverse(daysArray); @@ -36,5 +36,4 @@ public static Guid Generate() return new Guid(guidArray); } } -} - +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Impersonation/Windows/ConfigureWindowsIdentityEnricher.cs b/src/NServiceBus.Core/Impersonation/Windows/ConfigureWindowsIdentityEnricher.cs deleted file mode 100644 index 3c5cf43f592..00000000000 --- a/src/NServiceBus.Core/Impersonation/Windows/ConfigureWindowsIdentityEnricher.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NServiceBus.Impersonation.Windows -{ - class ConfigureWindowsIdentityEnricher : INeedInitialization - { - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(r=> r.ConfigureComponent(DependencyLifecycle.SingleInstance)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Impersonation/Windows/WindowsIdentityEnricher.cs b/src/NServiceBus.Core/Impersonation/Windows/WindowsIdentityEnricher.cs deleted file mode 100644 index 38afa2ef356..00000000000 --- a/src/NServiceBus.Core/Impersonation/Windows/WindowsIdentityEnricher.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Impersonation.Windows -{ - using System.Security.Principal; - using System.Threading; - using MessageMutator; - using Unicast.Messages; - - /// - /// Stamps outgoing messages with the current windows identity - /// - class WindowsIdentityEnricher : IMutateOutgoingTransportMessages - { - - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - if (Thread.CurrentPrincipal != null && Thread.CurrentPrincipal.Identity != null && !string.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name)) - { - transportMessage.Headers[Headers.WindowsIdentityName] = Thread.CurrentPrincipal.Identity.Name; - return; - } - var windowsIdentity = WindowsIdentity.GetCurrent(); - if (windowsIdentity != null) - { - transportMessage.Headers[Headers.WindowsIdentityName] = windowsIdentity.Name; - } - - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/InitializableEndpoint.cs b/src/NServiceBus.Core/InitializableEndpoint.cs new file mode 100644 index 00000000000..12614c5295e --- /dev/null +++ b/src/NServiceBus.Core/InitializableEndpoint.cs @@ -0,0 +1,188 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Security.Principal; + using System.Threading.Tasks; + using Config.ConfigurationSource; + using Features; + using Installation; + using ObjectBuilder; + using ObjectBuilder.Common; + using Pipeline; + using Settings; + using Transport; + + class InitializableEndpoint + { + public InitializableEndpoint(SettingsHolder settings, IContainer container, List> registrations, PipelineSettings pipelineSettings, PipelineConfiguration pipelineConfiguration) + { + this.settings = settings; + this.pipelineSettings = pipelineSettings; + this.pipelineConfiguration = pipelineConfiguration; + + RegisterContainerAdapter(container); + RunUserRegistrations(registrations); + + this.container.RegisterSingleton(this); + this.container.RegisterSingleton(settings); + } + + public async Task Initialize() + { + RegisterCriticalErrorHandler(); + var concreteTypes = settings.GetAvailableTypes() + .Where(IsConcrete) + .ToList(); + WireUpConfigSectionOverrides(concreteTypes); + + var featureActivator = BuildFeatureActivator(concreteTypes); + + ConfigRunBeforeIsFinalized(concreteTypes); + + var transportDefinition = settings.Get(); + var connectionString = settings.Get().GetConnectionStringOrRaiseError(transportDefinition); + var transportInfrastructure = transportDefinition.Initialize(settings, connectionString); + settings.Set(transportInfrastructure); + + var featureStats = featureActivator.SetupFeatures(container, pipelineSettings); + + pipelineConfiguration.RegisterBehaviorsInContainer(settings, container); + + DisplayDiagnosticsForFeatures.Run(featureStats); + + container.ConfigureComponent(b => settings.Get(), DependencyLifecycle.SingleInstance); + + await RunInstallers(concreteTypes).ConfigureAwait(false); + + var startableEndpoint = new StartableEndpoint(settings, builder, featureActivator, pipelineConfiguration, new EventAggregator(settings.Get()), transportInfrastructure, criticalError); + return startableEndpoint; + } + + static bool IsConcrete(Type x) + { + return !x.IsAbstract && !x.IsInterface; + } + + void ConfigRunBeforeIsFinalized(IEnumerable concreteTypes) + { + foreach (var instanceToInvoke in concreteTypes.Where(IsIWantToRunBeforeConfigurationIsFinalized) + .Select(type => (IWantToRunBeforeConfigurationIsFinalized)Activator.CreateInstance(type))) + { + instanceToInvoke.Run(settings); + } + } + + static bool IsIWantToRunBeforeConfigurationIsFinalized(Type type) + { + return typeof(IWantToRunBeforeConfigurationIsFinalized).IsAssignableFrom(type); + } + + FeatureActivator BuildFeatureActivator(IEnumerable concreteTypes) + { + var featureActivator = new FeatureActivator(settings); + foreach (var type in concreteTypes.Where(IsFeature)) + { + featureActivator.Add(type.Construct()); + } + return featureActivator; + } + + static bool IsFeature(Type type) + { + return typeof(Feature).IsAssignableFrom(type); + } + + void RegisterCriticalErrorHandler() + { + Func errorAction; + settings.TryGet("onCriticalErrorAction", out errorAction); + criticalError = new CriticalError(errorAction); + container.RegisterSingleton(criticalError); + } + + void RunUserRegistrations(List> registrations) + { + foreach (var registration in registrations) + { + registration(container); + } + } + + void RegisterContainerAdapter(IContainer containerToAdapt) + { + var b = new CommonObjectBuilder(containerToAdapt); + + builder = b; + container = b; + + container.ConfigureComponent(_ => b, DependencyLifecycle.SingleInstance); + } + + void WireUpConfigSectionOverrides(IEnumerable concreteTypes) + { + foreach (var type in concreteTypes.Where(ImplementsIProvideConfiguration)) + { + container.ConfigureComponent(type, DependencyLifecycle.InstancePerCall); + } + } + + static bool ImplementsIProvideConfiguration(Type type) + { + return type.GetInterfaces().Any(IsIProvideConfiguration); + } + + static bool IsIProvideConfiguration(Type type) + { + if (!type.IsGenericType) + { + return false; + } + + var args = type.GetGenericArguments(); + if (args.Length != 1) + { + return false; + } + + return typeof(IProvideConfiguration<>).MakeGenericType(args) + .IsAssignableFrom(type); + } + + async Task RunInstallers(IEnumerable concreteTypes) + { + if (Debugger.IsAttached || settings.GetOrDefault("Installers.Enable")) + { + foreach (var installerType in concreteTypes.Where(t => IsINeedToInstallSomething(t))) + { + container.ConfigureComponent(installerType, DependencyLifecycle.InstancePerCall); + } + + var username = GetInstallationUserName(); + foreach (var installer in builder.BuildAll()) + { + await installer.Install(username).ConfigureAwait(false); + } + } + } + + static bool IsINeedToInstallSomething(Type t) => typeof(INeedToInstallSomething).IsAssignableFrom(t); + + string GetInstallationUserName() + { + string username; + return settings.TryGet("Installers.UserName", out username) + ? username + : WindowsIdentity.GetCurrent().Name; + } + + IBuilder builder; + IConfigureComponents container; + PipelineConfiguration pipelineConfiguration; + PipelineSettings pipelineSettings; + SettingsHolder settings; + CriticalError criticalError; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Installation/INeedToInstallSomething.cs b/src/NServiceBus.Core/Installation/INeedToInstallSomething.cs index f59adadc08f..3d07f4de6ef 100644 --- a/src/NServiceBus.Core/Installation/INeedToInstallSomething.cs +++ b/src/NServiceBus.Core/Installation/INeedToInstallSomething.cs @@ -1,5 +1,7 @@ namespace NServiceBus.Installation { + using System.Threading.Tasks; + /// /// Interface invoked by the infrastructure when going to install an endpoint. /// @@ -9,7 +11,6 @@ public interface INeedToInstallSomething /// Performs the installation providing permission for the given user. /// /// The user for whom permissions will be given. - /// instance. - void Install(string identity, Configure config); + Task Install(string identity); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Installation/InstallConfigExtensions.cs b/src/NServiceBus.Core/Installation/InstallConfigExtensions.cs index 6000e64727a..f85bdaa04d9 100644 --- a/src/NServiceBus.Core/Installation/InstallConfigExtensions.cs +++ b/src/NServiceBus.Core/Installation/InstallConfigExtensions.cs @@ -1,27 +1,26 @@ namespace NServiceBus { - using Config; using Installation; - using NServiceBus.Features; /// - /// Convenience methods for configuring how instances of s are run. + /// Convenience methods for configuring how instances of s are run. /// - public static partial class InstallConfigExtensions + public static class InstallConfigExtensions { /// - /// Enable all to run when . + /// Enable all to run when the configuration is complete. /// - /// The instance of to apply these settings to. - /// The username to pass to - public static void EnableInstallers(this BusConfiguration config, string username = null) + /// The instance to apply the settings to. + /// The username to pass to . + public static void EnableInstallers(this EndpointConfiguration config, string username = null) { + Guard.AgainstNull(nameof(config), config); if (username != null) { - config.Settings.Set(InstallationSupport.UsernameKey, username); + config.Settings.Set("Installers.UserName", username); } - config.EnableFeature(); + config.Settings.Set("Installers.Enable", true); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Installation/InstallConfigExtensions_obsolete.cs b/src/NServiceBus.Core/Installation/InstallConfigExtensions_obsolete.cs deleted file mode 100644 index 5d6431ce345..00000000000 --- a/src/NServiceBus.Core/Installation/InstallConfigExtensions_obsolete.cs +++ /dev/null @@ -1,18 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class InstallConfigExtensions - { - [ObsoleteEx( - Message = "Use `configuration.EnableInstallers()`, where configuration is an instance of type `BusConfiguration`.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure EnableInstallers(this Configure config, string username = null) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Installation/InstallationSupport.cs b/src/NServiceBus.Core/Installation/InstallationSupport.cs deleted file mode 100644 index 86a010b282d..00000000000 --- a/src/NServiceBus.Core/Installation/InstallationSupport.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Security.Principal; - using Config; - using Installation; - using ObjectBuilder; - using Settings; - - /// - /// Provides support for running installers - /// - public class InstallationSupport : Feature - { - internal const string UsernameKey = "installation.username"; - - internal InstallationSupport() - { - if (Debugger.IsAttached) - { - EnableByDefault(); - } - } - - /// - /// Invoked if the feature is activated - /// - /// The feature context - protected internal override void Setup(FeatureConfigurationContext context) - { - foreach (var installerType in GetInstallerTypes(context)) - { - context.Container.ConfigureComponent(installerType, DependencyLifecycle.InstancePerCall); - } - } - - static IEnumerable GetInstallerTypes(FeatureConfigurationContext context) - { - return context.Settings.GetAvailableTypes() - .Where(t => typeof(INeedToInstallSomething).IsAssignableFrom(t) && !(t.IsAbstract || t.IsInterface)); - } - - class Starter : IWantToRunWhenConfigurationIsComplete - { - IBuilder builder; - ReadOnlySettings readOnlySettings; - Configure configure; - - public Starter(IBuilder builder, ReadOnlySettings readOnlySettings, Configure configure) - { - this.builder = builder; - this.readOnlySettings = readOnlySettings; - this.configure = configure; - } - - string GetInstallationUserName(ReadOnlySettings settings) - { - string username; - if (settings.TryGet(UsernameKey, out username)) - { - return username; - } - - return WindowsIdentity.GetCurrent().Name; - } - - public void Run(Configure config) - { - var username = GetInstallationUserName(readOnlySettings); - foreach (var installer in builder.BuildAll()) - { - installer.Install(username, configure); - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/InternalsVisibleTo.cs b/src/NServiceBus.Core/InternalsVisibleTo.cs index 6b43d8973a2..46bff2d8d5b 100644 --- a/src/NServiceBus.Core/InternalsVisibleTo.cs +++ b/src/NServiceBus.Core/InternalsVisibleTo.cs @@ -3,4 +3,4 @@ [assembly: InternalsVisibleTo("NServiceBus.Hosting.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: InternalsVisibleTo("NServiceBus.Core.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007f16e21368ff041183fab592d9e8ed37e7be355e93323147a1d29983d6e591b04282e4da0c9e18bd901e112c0033925eb7d7872c2f1706655891c5c9d57297994f707d16ee9a8f40d978f064ee1ffc73c0db3f4712691b23bf596f75130f4ec978cf78757ec034625a5f27e6bb50c618931ea49f6f628fd74271c32959efb1c5")] [assembly: InternalsVisibleTo("NServiceBus.PerformanceTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007f16e21368ff041183fab592d9e8ed37e7be355e93323147a1d29983d6e591b04282e4da0c9e18bd901e112c0033925eb7d7872c2f1706655891c5c9d57297994f707d16ee9a8f40d978f064ee1ffc73c0db3f4712691b23bf596f75130f4ec978cf78757ec034625a5f27e6bb50c618931ea49f6f628fd74271c32959efb1c5")] -[assembly: InternalsVisibleTo("ReturnToSourceQueue, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: InternalsVisibleTo("NServiceBus.AcceptanceTesting, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] diff --git a/src/NServiceBus.Core/Licensing/AuditInvalidLicenseBehavior.cs b/src/NServiceBus.Core/Licensing/AuditInvalidLicenseBehavior.cs new file mode 100644 index 00000000000..fcea45e39bd --- /dev/null +++ b/src/NServiceBus.Core/Licensing/AuditInvalidLicenseBehavior.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System; + using System.Diagnostics; + using System.Threading.Tasks; + using Logging; + using Pipeline; + + class AuditInvalidLicenseBehavior : IBehavior + { + public async Task Invoke(IAuditContext context, Func next) + { + context.AddAuditData(Headers.HasLicenseExpired, "true"); + + await next(context).ConfigureAwait(false); + + if (Debugger.IsAttached) + { + Log.Error("Your license has expired"); + } + } + + static ILog Log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions.cs b/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions.cs index c71237bec0b..8079563fed5 100644 --- a/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions.cs +++ b/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions.cs @@ -1,50 +1,47 @@ namespace NServiceBus { - using System; using System.IO; - using Licensing; + using Features; using Logging; /// /// Contains extension methods to configure license. /// - public static partial class ConfigureLicenseExtensions + public static class ConfigureLicenseExtensions { - static ILog Logger = LogManager.GetLogger(typeof(LicenseManager)); - /// /// Allows user to specify the license string. /// - /// The current . + /// The instance to apply the settings to. /// The license text. -// ReSharper disable UnusedParameter.Global - public static void License(this BusConfiguration config, string licenseText) -// ReSharper restore UnusedParameter.Global + public static void License(this EndpointConfiguration config, string licenseText) { - if (string.IsNullOrWhiteSpace(licenseText)) - { - throw new ArgumentException("licenseText is required", "licenseText"); - } - Logger.Info(@"Using license supplied via fluent API."); - LicenseManager.InitializeLicenseText(licenseText); + Guard.AgainstNullAndEmpty(nameof(licenseText), licenseText); + Guard.AgainstNull(nameof(config), config); + Logger.Info("Using license supplied via fluent API."); + config.Settings.Set(LicenseReminder.LicenseTextSettingsKey, licenseText); } /// /// Allows user to specify the path for the license file. /// - /// The current . + /// The instance to apply the settings to. /// A relative or absolute path to the license file. - public static void LicensePath(this BusConfiguration config, string licenseFile) + public static void LicensePath(this EndpointConfiguration config, string licenseFile) { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(licenseFile), licenseFile); if (!File.Exists(licenseFile)) { throw new FileNotFoundException("License file not found", licenseFile); } var licenseText = NonLockingFileReader.ReadAllTextWithoutLocking(licenseFile); - + config.License(licenseText); } + + static ILog Logger = LogManager.GetLogger(typeof(LicenseManager)); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions_Obsolete.cs b/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions_Obsolete.cs deleted file mode 100644 index 0da28d93888..00000000000 --- a/src/NServiceBus.Core/Licensing/ConfigureLicenseExtensions_Obsolete.cs +++ /dev/null @@ -1,29 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigureLicenseExtensions - { - - [ObsoleteEx( - Message = "Use `configuration.License(licenseText)`, where configuration is an instance of type `BusConfiguration`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure License(this Configure config, string licenseText) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - Message = "Use `configuration.LicensePath(licenseFile)`, where configuration is an instance of type `BusConfiguration`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure LicensePath(this Configure config, string licenseFile) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.Core/Licensing/LicenseBehavior.cs b/src/NServiceBus.Core/Licensing/LicenseBehavior.cs deleted file mode 100644 index 3b9e863bc00..00000000000 --- a/src/NServiceBus.Core/Licensing/LicenseBehavior.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Diagnostics; - using Logging; - using NServiceBus.Licensing; - using Pipeline; - using Pipeline.Contexts; - - class LicenseBehavior : IBehavior - { - public bool LicenseExpired { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - context.PhysicalMessage.Headers[Headers.HasLicenseExpired] = LicenseExpired.ToString().ToLower(); - - next(); - - if (Debugger.IsAttached) - { - if (LicenseManager.HasLicenseExpired()) - { - Log.Error("Your license has expired"); - } - } - } - - static ILog Log = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/LicenseExpiredForm.Designer.cs b/src/NServiceBus.Core/Licensing/LicenseExpiredForm.Designer.cs index 3935e9c0dc0..9756912d7ab 100644 --- a/src/NServiceBus.Core/Licensing/LicenseExpiredForm.Designer.cs +++ b/src/NServiceBus.Core/Licensing/LicenseExpiredForm.Designer.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { partial class LicenseExpiredForm { diff --git a/src/NServiceBus.Core/Licensing/LicenseExpiredForm.cs b/src/NServiceBus.Core/Licensing/LicenseExpiredForm.cs index 5d3ba516490..2ebd7e4d4fe 100644 --- a/src/NServiceBus.Core/Licensing/LicenseExpiredForm.cs +++ b/src/NServiceBus.Core/Licensing/LicenseExpiredForm.cs @@ -1,16 +1,17 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System; using System.Diagnostics; + using System.Globalization; using System.Windows.Forms; using Janitor; using Logging; + using Microsoft.Win32; using Particular.Licensing; [SkipWeaving] partial class LicenseExpiredForm : Form { - static ILog Logger = LogManager.GetLogger(); public LicenseExpiredForm() { InitializeComponent(); @@ -24,10 +25,10 @@ protected override void OnLoad(EventArgs e) warningText.Text = "The trial period is now over"; - if (CurrentLicense != null && CurrentLicense.IsTrialLicense) + if (CurrentLicense != null && !CurrentLicense.IsExtendedTrial) { Text = "NServiceBus - Initial Trial Expired"; - instructionsText.Text = "To extend your free trial, click 'Extend trial' and register online. When you receive your license file, save it to disk and then click the 'Browse' button below to select it."; + instructionsText.Text = "To extend the free trial, click 'Extend trial' and register online. When the license file is received, save it to disk and then click the 'Browse' button below to select it."; getTrialLicenseButton.Text = "Extend Trial"; purchaseButton.Visible = false; getTrialLicenseButton.Left = purchaseButton.Left; @@ -35,7 +36,7 @@ protected override void OnLoad(EventArgs e) else { Text = "NServiceBus - Extended Trial Expired"; - instructionsText.Text = "Please click 'Contact Sales' to request an extension to your free trial, or click 'Buy Now' to purchase a license online. When you receive your license file, save it to disk and then click the 'Browse' button below to select it."; + instructionsText.Text = "Click 'Contact Sales' to request an extension to the free trial, or click 'Buy Now' to purchase a license online. When the license file is received, save it to disk and then click the 'Browse' button below to select it."; getTrialLicenseButton.Text = "Contact Sales"; } @@ -61,7 +62,7 @@ void browseButton_Click(object sender, EventArgs e) if (LicenseExpirationChecker.HasLicenseExpired(license)) { - var message = "The license you provided has expired, please select another file."; + var message = "The license you provided has expired, select another file."; Logger.Warn(message); MessageBox.Show(this, message, "License expired", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; @@ -71,7 +72,7 @@ void browseButton_Click(object sender, EventArgs e) } catch (Exception exception) { - var message = string.Format("An error occurred when parsing the license.\r\nMessage: {0}\r\nThe exception details have been appended to your log.", exception.Message); + var message = $"An error occurred when parsing the license.\r\nMessage: {exception.Message}\r\nThe exception details have been appended to the log."; Logger.Warn("Error parsing license", exception); MessageBox.Show(this, message, "Error parsing license", MessageBoxButtons.OK, MessageBoxIcon.Error); } @@ -79,23 +80,58 @@ void browseButton_Click(object sender, EventArgs e) } } - public string ResultingLicenseText; - void PurchaseButton_Click(object sender, EventArgs e) { - Process.Start("http://particular.net/licensing"); + Process.Start("https://particular.net/licensing"); } - private void getTrialLicenseButton_Click(object sender, EventArgs e) + void getTrialLicenseButton_Click(object sender, EventArgs e) { - if (CurrentLicense != null && CurrentLicense.IsTrialLicense) + + string baseUrl; + if (CurrentLicense != null && !CurrentLicense.IsExtendedTrial) { - Process.Start("http://particular.net/extend-your-trial-14"); + // Original 14 day trial expired, give the user a chance to extend trial + baseUrl = "https://particular.net/extend-nservicebus-trial"; } else { - Process.Start("http://particular.net/extend-your-trial-45"); + // Extended trial license expired, ask the user to Contact Sales + baseUrl = "https://particular.net/extend-your-trial-45"; + } + + var trialStart = TrialStartDateStore.GetTrialStartDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + var url = $"{baseUrl}?NugetUser={IsNugetUser()}&PlatformInstaller={HasUserInstalledPlatform()}&TrialStartDate={trialStart}"; + + // Open the url with the querystrings + Process.Start(url); + } + + static string IsNugetUser() + { + using (var regRoot = Registry.CurrentUser.OpenSubKey(@"Software\ParticularSoftware")) + { + if (regRoot != null) + { + return (string)regRoot.GetValue("NuGetUser", "false"); + } + } + return bool.FalseString; + } + + static string HasUserInstalledPlatform() + { + using (var regRoot = Registry.CurrentUser.OpenSubKey(@"Software\ParticularSoftware\PlatformInstaller")) + { + if (regRoot != null) + { + return bool.TrueString; + } } + return bool.FalseString; } + + public string ResultingLicenseText; + static ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/LicenseExpiredFormDisplayer.cs b/src/NServiceBus.Core/Licensing/LicenseExpiredFormDisplayer.cs index 7fc48ff53a3..93a9093cae0 100644 --- a/src/NServiceBus.Core/Licensing/LicenseExpiredFormDisplayer.cs +++ b/src/NServiceBus.Core/Licensing/LicenseExpiredFormDisplayer.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System.Threading; using Particular.Licensing; @@ -25,7 +25,6 @@ public static License PromptUserForLicense(License currentLicense) return LicenseDeserializer.Deserialize(form.ResultingLicenseText); } - } finally { diff --git a/src/NServiceBus.Core/Licensing/LicenseLocationConventions.cs b/src/NServiceBus.Core/Licensing/LicenseLocationConventions.cs index 0db940f145a..10471bc3184 100644 --- a/src/NServiceBus.Core/Licensing/LicenseLocationConventions.cs +++ b/src/NServiceBus.Core/Licensing/LicenseLocationConventions.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System; using System.Configuration; @@ -8,69 +8,67 @@ namespace NServiceBus.Licensing static class LicenseLocationConventions { - static ILog Logger = LogManager.GetLogger(typeof(LicenseLocationConventions)); - public static string TryFindLicenseText() { var appConfigLicenseString = ConfigurationManager.AppSettings["NServiceBus/License"]; - if (!String.IsNullOrEmpty(appConfigLicenseString)) + if (!string.IsNullOrEmpty(appConfigLicenseString)) { - Logger.Info(@"Using embedded license supplied via config file AppSettings/NServiceBus/License."); + Logger.Info("Using embedded license supplied via config file AppSettings/NServiceBus/License."); return appConfigLicenseString; } var appConfigLicenseFile = ConfigurationManager.AppSettings["NServiceBus/LicensePath"]; - if (!String.IsNullOrEmpty(appConfigLicenseFile)) + if (!string.IsNullOrEmpty(appConfigLicenseFile)) { if (File.Exists(appConfigLicenseFile)) { - Logger.InfoFormat(@"Using license supplied via config file AppSettings/NServiceBus/LicensePath ({0}).", appConfigLicenseFile); + Logger.InfoFormat("Using license supplied via config file AppSettings/NServiceBus/LicensePath ({0}).", appConfigLicenseFile); return NonLockingFileReader.ReadAllTextWithoutLocking(appConfigLicenseFile); } //TODO: should we throw if file does not exist? - throw new Exception(string.Format("You have a configured licensing via AppConfigLicenseFile to use the file at '{0}'. However this file does not exist. Either place a valid license at this location or remove the app setting.", appConfigLicenseFile)); + throw new Exception($"You have a configured licensing via AppConfigLicenseFile to use the file at '{appConfigLicenseFile}'. However this file does not exist. Either place a valid license at this location or remove the app setting."); } var localLicenseFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"NServiceBus\License.xml"); if (File.Exists(localLicenseFile)) { - Logger.InfoFormat(@"Using license in current folder ({0}).", localLicenseFile); + Logger.InfoFormat("Using license in current folder ({0}).", localLicenseFile); return NonLockingFileReader.ReadAllTextWithoutLocking(localLicenseFile); } var oldLocalLicenseFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"License\License.xml"); if (File.Exists(oldLocalLicenseFile)) { - Logger.InfoFormat(@"Using license in current folder ({0}).", oldLocalLicenseFile); + Logger.InfoFormat("Using license in current folder ({0}).", oldLocalLicenseFile); return NonLockingFileReader.ReadAllTextWithoutLocking(oldLocalLicenseFile); } var registryLicense = LoadLicenseFromRegistry(); - if (!String.IsNullOrEmpty(registryLicense)) + if (!string.IsNullOrEmpty(registryLicense)) { return registryLicense; } registryLicense = LoadLicenseFromPreviousRegistryLocation("4.3"); - if (!String.IsNullOrEmpty(registryLicense)) + if (!string.IsNullOrEmpty(registryLicense)) { return registryLicense; } registryLicense = LoadLicenseFromPreviousRegistryLocation("4.2"); - if (!String.IsNullOrEmpty(registryLicense)) + if (!string.IsNullOrEmpty(registryLicense)) { return registryLicense; } registryLicense = LoadLicenseFromPreviousRegistryLocation("4.1"); - if (!String.IsNullOrEmpty(registryLicense)) + if (!string.IsNullOrEmpty(registryLicense)) { return registryLicense; } registryLicense = LoadLicenseFromPreviousRegistryLocation("4.0"); - if (!String.IsNullOrEmpty(registryLicense)) + if (!string.IsNullOrEmpty(registryLicense)) { return registryLicense; } @@ -81,8 +79,8 @@ public static string TryFindLicenseText() static string LoadLicenseFromRegistry() { var hkcuLicense = GetHKCULicense(@"ParticularSoftware\NServiceBus"); - - if (!String.IsNullOrEmpty(hkcuLicense)) + + if (!string.IsNullOrEmpty(hkcuLicense)) { Logger.Info(@"Using embedded license found in registry [HKEY_CURRENT_USER\Software\ParticularSoftware\NServiceBus\License]."); @@ -90,7 +88,7 @@ static string LoadLicenseFromRegistry() } var hklmLicense = GetHKLMLicense(@"ParticularSoftware\NServiceBus"); - if (!String.IsNullOrEmpty(hklmLicense)) + if (!string.IsNullOrEmpty(hklmLicense)) { Logger.Info(@"Using embedded license found in registry [HKEY_LOCAL_MACHINE\Software\ParticularSoftware\NServiceBus\License]."); @@ -104,7 +102,7 @@ static string LoadLicenseFromPreviousRegistryLocation(string version) { var hkcuLicense = GetHKCULicense(subKey: version); - if (!String.IsNullOrEmpty(hkcuLicense)) + if (!string.IsNullOrEmpty(hkcuLicense)) { Logger.InfoFormat(@"Using embedded license found in registry [HKEY_CURRENT_USER\Software\NServiceBus\{0}\License].", version); @@ -112,7 +110,7 @@ static string LoadLicenseFromPreviousRegistryLocation(string version) } var hklmLicense = GetHKLMLicense(subKey: version); - if (!String.IsNullOrEmpty(hklmLicense)) + if (!string.IsNullOrEmpty(hklmLicense)) { Logger.InfoFormat(@"Using embedded license found in registry [HKEY_LOCAL_MACHINE\Software\NServiceBus\{0}\License].", version); @@ -125,7 +123,7 @@ static string LoadLicenseFromPreviousRegistryLocation(string version) static string GetHKCULicense(string softwareKey = "NServiceBus", string subKey = null) { var keyPath = @"SOFTWARE\" + softwareKey; - + if (subKey != null) { keyPath += @"\" + subKey; @@ -133,12 +131,8 @@ static string GetHKCULicense(string softwareKey = "NServiceBus", string subKey = using (var registryKey = Registry.CurrentUser.OpenSubKey(keyPath)) { - if (registryKey != null) - { - return (string) registryKey.GetValue("License", null); - } + return (string) registryKey?.GetValue("License", null); } - return null; } static string GetHKLMLicense(string softwareKey = "NServiceBus", string subKey = null) @@ -160,12 +154,14 @@ static string GetHKLMLicense(string softwareKey = "NServiceBus", string subKey = } } } - // ReSharper disable once EmptyGeneralCatchClause + // ReSharper disable once EmptyGeneralCatchClause catch (Exception) { //Swallow exception if we can't read HKLM } return null; } + + static ILog Logger = LogManager.GetLogger(typeof(LicenseLocationConventions)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/LicenseManager.cs b/src/NServiceBus.Core/Licensing/LicenseManager.cs index 38570b83918..875c9d05ace 100644 --- a/src/NServiceBus.Core/Licensing/LicenseManager.cs +++ b/src/NServiceBus.Core/Licensing/LicenseManager.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System.Diagnostics; using System.Threading; @@ -7,67 +7,14 @@ namespace NServiceBus.Licensing using Microsoft.Win32; using Particular.Licensing; - static class LicenseManager + class LicenseManager { - internal static bool HasLicenseExpired() + internal bool HasLicenseExpired() { return license == null || LicenseExpirationChecker.HasLicenseExpired(license); } - internal static void InitializeLicenseText(string license) - { - licenseText = license; - } - - internal static void PromptUserForLicenseIfTrialHasExpired() - { - if (!(Debugger.IsAttached && SystemInformation.UserInteractive)) - { - //We only prompt user if user is in debugging mode and we are running in interactive mode - return; - } - - bool createdNew; - using (new Mutex(true, string.Format("NServiceBus-{0}", GitFlowVersion.MajorMinor), out createdNew)) - { - if (!createdNew) - { - //Dialog already displaying for this software version by another process, so we just use the already assigned license. - return; - } - - if (license == null || LicenseExpirationChecker.HasLicenseExpired(license)) - { - var licenseProvidedByUser = LicenseExpiredFormDisplayer.PromptUserForLicense(license); - - if (licenseProvidedByUser != null) - { - license = licenseProvidedByUser; - } - } - } - } - - static License GetTrialLicense() - { - var trialStartDate = TrialStartDateStore.GetTrialStartDate(); - var trialLicense = License.TrialLicense(trialStartDate); - - //Check trial is still valid - if (LicenseExpirationChecker.HasLicenseExpired(trialLicense)) - { - Logger.WarnFormat("Trial for the Particular Service Platform has expired"); - } - else - { - var message = string.Format("Trial for Particular Service Platform is still active, trial expires on {0}.", trialLicense.ExpirationDate.Value.ToLocalTime().ToShortDateString()); - Logger.Info(message); - } - - return trialLicense; - } - - internal static void InitializeLicense() + internal void InitializeLicense(string licenseText) { //only do this if not been configured by the fluent API if (licenseText == null) @@ -78,6 +25,7 @@ internal static void InitializeLicense() if (string.IsNullOrWhiteSpace(licenseText)) { license = GetTrialLicense(); + PromptUserForLicenseIfTrialHasExpired(); return; } @@ -87,22 +35,49 @@ internal static void InitializeLicense() if (LicenseExpirationChecker.HasLicenseExpired(foundLicense)) { - Logger.Fatal(" You can renew it at http://particular.net/licensing."); + // If the found license is a trial license then it is actually a extended trial license not a locally generated trial. + // Set the property to indicate that it is an extended license as it's not set by the license generation + if (foundLicense.IsTrialLicense) + { + foundLicense.IsExtendedTrial = true; + PromptUserForLicenseIfTrialHasExpired(); + return; + } + Logger.Fatal("Your license has expired! You can renew it at https://particular.net/licensing."); return; } if (foundLicense.UpgradeProtectionExpiration != null) { - Logger.InfoFormat("UpgradeProtectionExpiration: {0}", foundLicense.UpgradeProtectionExpiration); + Logger.InfoFormat("License upgrade protection expires on: {0}", foundLicense.UpgradeProtectionExpiration); } else { - Logger.InfoFormat("Expires on {0}", foundLicense.ExpirationDate); + Logger.InfoFormat("License expires on {0}", foundLicense.ExpirationDate); } license = foundLicense; } + static License GetTrialLicense() + { + var trialStartDate = TrialStartDateStore.GetTrialStartDate(); + var trialLicense = License.TrialLicense(trialStartDate); + + //Check trial is still valid + if (LicenseExpirationChecker.HasLicenseExpired(trialLicense)) + { + Logger.WarnFormat("Trial for the Particular Service Platform has expired"); + } + else + { + var message = $"Trial for Particular Service Platform is still active, trial expires on {trialLicense.ExpirationDate.Value.ToLocalTime().ToShortDateString()}."; + Logger.Info(message); + } + + return trialLicense; + } + static string GetExistingLicense() { string existingLicense; @@ -122,10 +97,37 @@ static string GetExistingLicense() return LicenseLocationConventions.TryFindLicenseText(); } - static ILog Logger = LogManager.GetLogger(typeof(LicenseManager)); - static string licenseText; - static License license; + void PromptUserForLicenseIfTrialHasExpired() + { + if (!(Debugger.IsAttached && SystemInformation.UserInteractive)) + { + //We only prompt user if user is in debugging mode and we are running in interactive mode + return; + } + + bool createdNew; + using (new Mutex(true, $"NServiceBus-{GitFlowVersion.MajorMinor}", out createdNew)) + { + if (!createdNew) + { + //Dialog already displaying for this software version by another process, so we just use the already assigned license. + return; + } + + if (license == null || LicenseExpirationChecker.HasLicenseExpired(license)) + { + var licenseProvidedByUser = LicenseExpiredFormDisplayer.PromptUserForLicense(license); + + if (licenseProvidedByUser != null) + { + license = licenseProvidedByUser; + } + } + } + } + License license; + static ILog Logger = LogManager.GetLogger(typeof(LicenseManager)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/LicenseReminder.cs b/src/NServiceBus.Core/Licensing/LicenseReminder.cs index 3add5674d3e..9711e81633c 100644 --- a/src/NServiceBus.Core/Licensing/LicenseReminder.cs +++ b/src/NServiceBus.Core/Licensing/LicenseReminder.cs @@ -1,37 +1,49 @@ namespace NServiceBus.Features { using System; + using System.Diagnostics; using Logging; - using NServiceBus.Licensing; class LicenseReminder : Feature { public LicenseReminder() { EnableByDefault(); + + Defaults(s => s.SetDefault(LicenseTextSettingsKey, null)); } protected internal override void Setup(FeatureConfigurationContext context) { - var expiredLicense = true; try { - LicenseManager.InitializeLicense(); + var licenseManager = new LicenseManager(); + licenseManager.InitializeLicense(context.Settings.Get(LicenseTextSettingsKey)); + + context.Container.RegisterSingleton(licenseManager); + + var licenseExpired = licenseManager.HasLicenseExpired(); + if (!licenseExpired) + { + return; + } + + context.Pipeline.Register("LicenseReminder", new AuditInvalidLicenseBehavior(), "Audits that the message was processed by an endpoint with an expired license"); - expiredLicense = LicenseManager.HasLicenseExpired(); + if (Debugger.IsAttached) + { + context.Pipeline.Register("LogErrorOnInvalidLicense", new LogErrorOnInvalidLicenseBehavior(), "Logs an error when running in debug mode with an expired license"); + } } catch (Exception ex) { //we only log here to prevent licensing issue to abort startup and cause production outages Logger.Fatal("Failed to initialize the license", ex); } - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.LicenseExpired, expiredLicense); - - context.Pipeline.Register("LicenseReminder", typeof(LicenseBehavior), "Reminds users if license has expired"); } + public const string LicenseTextSettingsKey = "LicenseText"; + static ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/LogErrorOnInvalidLicenseBehavior.cs b/src/NServiceBus.Core/Licensing/LogErrorOnInvalidLicenseBehavior.cs new file mode 100644 index 00000000000..3c1a3bd3763 --- /dev/null +++ b/src/NServiceBus.Core/Licensing/LogErrorOnInvalidLicenseBehavior.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Logging; + using Pipeline; + + class LogErrorOnInvalidLicenseBehavior : IBehavior + { + public Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + Log.Error("Your license has expired"); + + return next(context); + } + + static ILog Log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Licensing/NonLockingFileReader.cs b/src/NServiceBus.Core/Licensing/NonLockingFileReader.cs index 5fc8f71e32f..ac9c2449ef1 100644 --- a/src/NServiceBus.Core/Licensing/NonLockingFileReader.cs +++ b/src/NServiceBus.Core/Licensing/NonLockingFileReader.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System.IO; @@ -7,9 +7,11 @@ static class NonLockingFileReader internal static string ReadAllTextWithoutLocking(string path) { using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (var textReader = new StreamReader(fileStream)) { - return textReader.ReadToEnd(); + using (var textReader = new StreamReader(fileStream)) + { + return textReader.ReadToEnd(); + } } } } diff --git a/src/NServiceBus.Core/Licensing/StaThreadRunner.cs b/src/NServiceBus.Core/Licensing/StaThreadRunner.cs index 009efefbb5e..d18a4b094a0 100644 --- a/src/NServiceBus.Core/Licensing/StaThreadRunner.cs +++ b/src/NServiceBus.Core/Licensing/StaThreadRunner.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Licensing +namespace NServiceBus { using System; using System.Threading; @@ -9,10 +9,7 @@ public static T ShowDialogInSTA(Func func) { var result = default(T); - var thread = new Thread(() => - { - result = func(); - }); + var thread = new Thread(() => { result = func(); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); diff --git a/src/NServiceBus.Core/Logging/ColoredConsoleLogger.cs b/src/NServiceBus.Core/Logging/ColoredConsoleLogger.cs index aa222b1f97a..ddccd4a9273 100644 --- a/src/NServiceBus.Core/Logging/ColoredConsoleLogger.cs +++ b/src/NServiceBus.Core/Logging/ColoredConsoleLogger.cs @@ -1,21 +1,20 @@ -namespace NServiceBus.Logging +namespace NServiceBus { using System; using System.Runtime.InteropServices; + using Logging; static class ColoredConsoleLogger { - static bool logToConsole; - - [DllImport("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle(int nStdHandle); - static ColoredConsoleLogger() { const int STD_OUTPUT_HANDLE = -11; - logToConsole = GetStdHandle(STD_OUTPUT_HANDLE) != IntPtr.Zero; + logToConsole = GetStdHandle(STD_OUTPUT_HANDLE) != IntPtr.Zero; } + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle(int nStdHandle); + public static void Write(string message, LogLevel logLevel) { if (!logToConsole) @@ -47,6 +46,6 @@ static ConsoleColor GetColor(LogLevel logLevel) return ConsoleColor.White; } - + static bool logToConsole; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/DefaultFactory.cs b/src/NServiceBus.Core/Logging/DefaultFactory.cs index 7337ac3d56d..7e65f4399f0 100644 --- a/src/NServiceBus.Core/Logging/DefaultFactory.cs +++ b/src/NServiceBus.Core/Logging/DefaultFactory.cs @@ -4,16 +4,15 @@ namespace NServiceBus.Logging using System.IO; using System.Web; using System.Web.Hosting; - using IODirectory=System.IO.Directory; + using IODirectory = System.IO.Directory; /// - /// The default . + /// The default . /// public class DefaultFactory : LoggingFactoryDefinition { - /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// public DefaultFactory() { @@ -22,40 +21,33 @@ public DefaultFactory() } /// - /// . + /// . /// protected internal override ILoggerFactory GetLoggingFactory() { var loggerFactory = new DefaultLoggerFactory(level.Value, directory.Value); - var message = string.Format("Logging to '{0}' with level {1}", directory, level); - loggerFactory.Write(GetType().Name,LogLevel.Info,message); + var message = $"Logging to '{directory}' with level {level}"; + loggerFactory.Write(GetType().Name, LogLevel.Info, message); return loggerFactory; } - Lazy level; - /// - /// Controls the . + /// Controls the . /// public void Level(LogLevel level) { this.level = new Lazy(() => level); } - Lazy directory; - /// /// The directory to log files to. /// public void Directory(string directory) { - if (string.IsNullOrWhiteSpace(directory)) - { - throw new ArgumentNullException("directory"); - } + Guard.AgainstNullAndEmpty(nameof(directory), directory); if (!IODirectory.Exists(directory)) { - var message = string.Format("Could not find logging directory: '{0}'", directory); + var message = $"Could not find logging directory: '{directory}'"; throw new DirectoryNotFoundException(message); } this.directory = new Lazy(() => directory); @@ -83,8 +75,8 @@ internal static string DeriveAppDataPath() { return appDataPath; } - - throw new Exception(GetMapPathError(string.Format("Failed since path returned ({0}) does not exist, please create it.", appDataPath))); + + throw new Exception(GetMapPathError($"Failed since path returned ({appDataPath}) does not exist. Ensure this directory is created and restart the endpoint.")); } static string TryMapPath() @@ -101,7 +93,11 @@ static string TryMapPath() static string GetMapPathError(string reason) { - return "Detected running in a website and attempted to use HostingEnvironment.MapPath(\"~/App_Data/\") to derive the logging path. " + reason + ". To avoid using HostingEnvironment.MapPath to derive the logging directory you can instead configure it to a specific path using LogManager.Use().Directory(\"pathToLoggingDirectory\");"; + return $"Detected running in a website and attempted to use HostingEnvironment.MapPath(\"~/App_Data/\") to derive the logging path. {reason}. To avoid using HostingEnvironment.MapPath to derive the logging directory you can instead configure it to a specific path using LogManager.Use().Directory(\"pathToLoggingDirectory\");"; } + + Lazy directory; + + Lazy level; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/DefaultLog.cs b/src/NServiceBus.Core/Logging/DefaultLog.cs index 1538a164bc9..7af1bf5f0b7 100644 --- a/src/NServiceBus.Core/Logging/DefaultLog.cs +++ b/src/NServiceBus.Core/Logging/DefaultLog.cs @@ -1,12 +1,10 @@ -namespace NServiceBus.Logging +namespace NServiceBus { using System; + using Logging; class NamedLogger : ILog { - string name; - readonly DefaultLoggerFactory defaultLoggerFactory; - public NamedLogger(string name, DefaultLoggerFactory defaultLoggerFactory) { this.name = name; @@ -31,7 +29,7 @@ public void Debug(string message, Exception exception) public void DebugFormat(string format, params object[] args) { - defaultLoggerFactory.Write(name, LogLevel.Debug, string.Format(format,args)); + defaultLoggerFactory.Write(name, LogLevel.Debug, string.Format(format, args)); } public void Info(string message) @@ -93,5 +91,8 @@ public void FatalFormat(string format, params object[] args) { defaultLoggerFactory.Write(name, LogLevel.Fatal, string.Format(format, args)); } + + DefaultLoggerFactory defaultLoggerFactory; + string name; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/DefaultLoggerFactory.cs b/src/NServiceBus.Core/Logging/DefaultLoggerFactory.cs index 282eddba2d7..229131191e0 100644 --- a/src/NServiceBus.Core/Logging/DefaultLoggerFactory.cs +++ b/src/NServiceBus.Core/Logging/DefaultLoggerFactory.cs @@ -1,19 +1,11 @@ -namespace NServiceBus.Logging +namespace NServiceBus { using System; using System.Diagnostics; + using Logging; class DefaultLoggerFactory : ILoggerFactory { - LogLevel filterLevel; - bool isDebugEnabled; - bool isInfoEnabled; - bool isWarnEnabled; - bool isErrorEnabled; - bool isFatalEnabled; - RollingLogger rollingLogger; - - object locker = new object(); public DefaultLoggerFactory(LogLevel filterLevel, string loggingDirectory) { this.filterLevel = filterLevel; @@ -38,7 +30,7 @@ public ILog GetLogger(string name) IsInfoEnabled = isInfoEnabled, IsWarnEnabled = isWarnEnabled, IsErrorEnabled = isErrorEnabled, - IsFatalEnabled = isFatalEnabled, + IsFatalEnabled = isFatalEnabled }; } @@ -50,7 +42,7 @@ public void Write(string name, LogLevel messageLevel, string message) } var datePart = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var paddedLevel = messageLevel.ToString().ToUpper().PadRight(5); - var fullMessage = string.Format("{0} {1} {2} {3}", datePart, paddedLevel, name, message); + var fullMessage = $"{datePart} {paddedLevel} {name} {message}"; lock (locker) { rollingLogger.Write(fullMessage); @@ -58,5 +50,15 @@ public void Write(string name, LogLevel messageLevel, string message) Trace.WriteLine(fullMessage); } } + + LogLevel filterLevel; + bool isDebugEnabled; + bool isErrorEnabled; + bool isFatalEnabled; + bool isInfoEnabled; + bool isWarnEnabled; + + object locker = new object(); + RollingLogger rollingLogger; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/ILog.cs b/src/NServiceBus.Core/Logging/ILog.cs index 723b8c594ae..4e11cacdb4d 100644 --- a/src/NServiceBus.Core/Logging/ILog.cs +++ b/src/NServiceBus.Core/Logging/ILog.cs @@ -1,116 +1,146 @@ namespace NServiceBus.Logging { using System; + using JetBrains.Annotations; /// - /// Provides logging methods and utility functions + /// Provides logging methods and utility functions. /// public interface ILog { /// - /// Gets a value indicating whether logging is enabled for the level. + /// Gets a value indicating whether logging is enabled for the level. /// bool IsDebugEnabled { get; } + /// - /// Gets a value indicating whether logging is enabled for the level. + /// Gets a value indicating whether logging is enabled for the level. /// bool IsInfoEnabled { get; } + /// - /// Gets a value indicating whether logging is enabled for the level. + /// Gets a value indicating whether logging is enabled for the level. /// bool IsWarnEnabled { get; } + /// - /// Gets a value indicating whether logging is enabled for the level. + /// Gets a value indicating whether logging is enabled for the level. /// bool IsErrorEnabled { get; } + /// - /// Gets a value indicating whether logging is enabled for the level. + /// Gets a value indicating whether logging is enabled for the level. /// bool IsFatalEnabled { get; } + /// - /// Writes the message at the level. + /// Writes the message at the level. /// /// Log message. void Debug(string message); + /// - /// Writes the message and exception at the level. + /// Writes the message and exception at the level. /// /// A string to be written. /// An exception to be logged. void Debug(string message, Exception exception); + /// - /// Writes the message at the level using the specified provider and format . + /// Writes the message at the level using the specified provider + /// and format . /// /// A string containing format items. /// Arguments to format. + [StringFormatMethod("format")] void DebugFormat(string format, params object[] args); + /// - /// Writes the message at the level. + /// Writes the message at the level. /// /// Log message. void Info(string message); + /// - /// Writes the message and exception at the level. + /// Writes the message and exception at the level. /// /// A string to be written. /// An exception to be logged. void Info(string message, Exception exception); + /// - /// Writes the message at the level using the specified provider and format . + /// Writes the message at the level using the specified provider + /// and format . /// /// A string containing format items. /// Arguments to format. + [StringFormatMethod("format")] void InfoFormat(string format, params object[] args); + /// - /// Writes the message at the level. + /// Writes the message at the level. /// /// Log message. void Warn(string message); + /// - /// Writes the message and exception at the level. + /// Writes the message and exception at the level. /// /// A string to be written. /// An exception to be logged. void Warn(string message, Exception exception); + /// - /// Writes the message at the level using the specified provider and format . + /// Writes the message at the level using the specified provider + /// and format . /// /// A string containing format items. /// Arguments to format. + [StringFormatMethod("format")] void WarnFormat(string format, params object[] args); + /// - /// Writes the message at the level. + /// Writes the message at the level. /// /// Log message. void Error(string message); + /// - /// Writes the message and exception at the level. + /// Writes the message and exception at the level. /// /// A string to be written. /// An exception to be logged. void Error(string message, Exception exception); + /// - /// Writes the message at the level using the specified provider and format . + /// Writes the message at the level using the specified provider + /// and format . /// /// A string containing format items. /// Arguments to format. + [StringFormatMethod("format")] void ErrorFormat(string format, params object[] args); + /// - /// Writes the message at the level. + /// Writes the message at the level. /// /// Log message. void Fatal(string message); + /// - /// Writes the message and exception at the level. + /// Writes the message and exception at the level. /// /// A string to be written. /// An exception to be logged. void Fatal(string message, Exception exception); + /// - /// Writes the message at the level using the specified provider and format . + /// Writes the message at the level using the specified provider + /// and format . /// /// A string containing format items. /// Arguments to format. + [StringFormatMethod("format")] void FatalFormat(string format, params object[] args); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/ILoggerFactory.cs b/src/NServiceBus.Core/Logging/ILoggerFactory.cs index 6f58b3db367..7da094cbedf 100644 --- a/src/NServiceBus.Core/Logging/ILoggerFactory.cs +++ b/src/NServiceBus.Core/Logging/ILoggerFactory.cs @@ -3,21 +3,22 @@ namespace NServiceBus.Logging using System; /// - /// Used by to facilitate redirecting logging to a different library. + /// Used by to facilitate redirecting logging to a different library. /// public interface ILoggerFactory { /// - /// Gets a for a specific . + /// Gets a for a specific . /// - /// The to get the for. - /// An instance of a specifically for . + /// The to get the for. + /// An instance of a specifically for . ILog GetLogger(Type type); + /// - /// Gets a for a specific . + /// Gets a for a specific . /// - /// The name of the usage to get the for. - /// An instance of a specifically for . + /// The name of the usage to get the for. + /// An instance of a specifically for . ILog GetLogger(string name); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/LogLevel.cs b/src/NServiceBus.Core/Logging/LogLevel.cs index 3b047a24e71..deaa0867065 100644 --- a/src/NServiceBus.Core/Logging/LogLevel.cs +++ b/src/NServiceBus.Core/Logging/LogLevel.cs @@ -1,28 +1,32 @@ namespace NServiceBus.Logging { /// - /// The allowed log levels. . + /// The allowed log levels. . /// public enum LogLevel { /// - /// Debug level messages + /// Debug level messages. /// Debug, + /// - /// Information level messages + /// Information level messages. /// Info, + /// - /// Warning level messages + /// Warning level messages. /// Warn, + /// - /// Error level messages + /// Error level messages. /// Error, + /// - /// Fatal level messages + /// Fatal level messages. /// Fatal } diff --git a/src/NServiceBus.Core/Logging/LogLevelReader.cs b/src/NServiceBus.Core/Logging/LogLevelReader.cs index 09da03be351..e7ffc959952 100644 --- a/src/NServiceBus.Core/Logging/LogLevelReader.cs +++ b/src/NServiceBus.Core/Logging/LogLevelReader.cs @@ -1,7 +1,8 @@ -namespace NServiceBus.Logging +namespace NServiceBus { using System; using System.Configuration; + using Logging; static class LogLevelReader { @@ -15,7 +16,7 @@ public static LogLevel GetDefaultLogLevel(LogLevel fallback = LogLevel.Info) if (!Enum.TryParse(threshold, true, out logLevel)) { var logLevels = string.Join(", ", Enum.GetNames(typeof(LogLevel))); - var message = string.Format("The value of '{0}' is invalid as a loglevel. Must be one of {1}.", threshold, logLevels); + var message = $"The value of '{threshold}' is invalid as a loglevel. Must be one of {logLevels}."; throw new Exception(message); } return logLevel; diff --git a/src/NServiceBus.Core/Logging/LogManager.cs b/src/NServiceBus.Core/Logging/LogManager.cs index 9d0906cdbeb..abd45359385 100644 --- a/src/NServiceBus.Core/Logging/LogManager.cs +++ b/src/NServiceBus.Core/Logging/LogManager.cs @@ -3,7 +3,8 @@ namespace NServiceBus.Logging using System; /// - /// Responsible for the creation of instances and used as an extension point to redirect log event to an external library. + /// Responsible for the creation of instances and used as an extension point to redirect log event to + /// an external library. /// /// /// The default logging will be to the console and a rolling log file. @@ -15,38 +16,33 @@ static LogManager() Use(); } - static Lazy loggerFactory; - /// - /// Used to inject an instance of into . + /// Used to inject an instance of into . /// public static T Use() where T : LoggingFactoryDefinition, new() { var loggingDefinition = new T(); - loggerFactory = new Lazy(loggingDefinition.GetLoggingFactory); - + loggerFactory = new Lazy(loggingDefinition.GetLoggingFactory); + return loggingDefinition; } /// - /// An instance of that will be used to construct s for static fields. + /// An instance of that will be used to construct s for static fields. /// /// - /// Replace this instance at application statup to redirect log event to your custom logging library. + /// Replace this instance at application statup to redirect log event to the custom logging library. /// public static void UseFactory(ILoggerFactory loggerFactory) { - if (loggerFactory == null) - { - throw new ArgumentNullException("loggerFactory"); - } + Guard.AgainstNull(nameof(loggerFactory), loggerFactory); LogManager.loggerFactory = new Lazy(() => loggerFactory); } /// - /// Construct a using as the name. + /// Construct a using as the name. /// public static ILog GetLogger() { @@ -54,19 +50,23 @@ public static ILog GetLogger() } /// - /// Construct a using as the name. + /// Construct a using as the name. /// public static ILog GetLogger(Type type) { + Guard.AgainstNull(nameof(type), type); return loggerFactory.Value.GetLogger(type); } /// - /// Construct a for . + /// Construct a for . /// public static ILog GetLogger(string name) { + Guard.AgainstNullAndEmpty(nameof(name), name); return loggerFactory.Value.GetLogger(name); } + + static Lazy loggerFactory; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/Logging.cs b/src/NServiceBus.Core/Logging/Logging.cs index 8399792809a..7f21ee45773 100644 --- a/src/NServiceBus.Core/Logging/Logging.cs +++ b/src/NServiceBus.Core/Logging/Logging.cs @@ -3,24 +3,18 @@ namespace NServiceBus.Config using System.Configuration; /// - /// Logging ConfigurationSection + /// Logging ConfigurationSection. /// public class Logging : ConfigurationSection { /// - /// The minimal logging level above which all calls to the log will be written + /// The minimal logging level above which all calls to the log will be written. /// - [ConfigurationProperty("Threshold", IsRequired = true,DefaultValue = "Info")] + [ConfigurationProperty("Threshold", IsRequired = true, DefaultValue = "Info")] public string Threshold { - get - { - return this["Threshold"] as string; - } - set - { - this["Threshold"] = value; - } + get { return this["Threshold"] as string; } + set { this["Threshold"] = value; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/LoggingDefinition.cs b/src/NServiceBus.Core/Logging/LoggingDefinition.cs index 503b995df5f..3d6e6dde093 100644 --- a/src/NServiceBus.Core/Logging/LoggingDefinition.cs +++ b/src/NServiceBus.Core/Logging/LoggingDefinition.cs @@ -1,14 +1,13 @@ namespace NServiceBus.Logging { /// - /// Base class for logging definitions + /// Base class for logging definitions. /// public abstract class LoggingFactoryDefinition { /// - /// Constructs an instance of for use by + /// Constructs an instance of for use by . /// protected internal abstract ILoggerFactory GetLoggingFactory(); - } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/RollingLogger.cs b/src/NServiceBus.Core/Logging/RollingLogger.cs index f2019878855..69ed3cb8ea8 100644 --- a/src/NServiceBus.Core/Logging/RollingLogger.cs +++ b/src/NServiceBus.Core/Logging/RollingLogger.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Logging +namespace NServiceBus { using System; using System.Collections.Generic; @@ -10,15 +10,6 @@ namespace NServiceBus.Logging class RollingLogger { - string targetDirectory; - int numberOfArchiveFilesToKeep; - long maxFileSize; - const long fileLimitInBytes = 10L * 1024 * 1024; //10MB - internal Func GetDate = () => DateTime.Now.Date; - DateTime lastWriteDate; - long currentFileSize; - protected string currentfilePath; - public RollingLogger(string targetDirectory, int numberOfArchiveFilesToKeep = 10, long maxFileSize = fileLimitInBytes) { this.targetDirectory = targetDirectory; @@ -29,13 +20,18 @@ public RollingLogger(string targetDirectory, int numberOfArchiveFilesToKeep = 10 public void Write(string message) { SyncFileSystem(); + InnerWrite(message); + } + + void InnerWrite(string message) + { try { AppendLine(message); } catch (Exception exception) { - var errorMessage = string.Format("NServiceBus.Logging.RollingLogger Could not write to log file {0} {1}", currentfilePath, exception); + var errorMessage = $"NServiceBus.RollingLogger Could not write to log file '{currentfilePath}'. Exception: {exception}"; Trace.WriteLine(errorMessage); } } @@ -74,7 +70,15 @@ void PurgeOldFiles(List logFiles) { foreach (var file in GetFilesToDelete(logFiles)) { - File.Delete(file); + try + { + File.Delete(file); + } + catch (Exception exception) + { + var errorMessage = $"NServiceBus.RollingLogger Could not purge log file '{file}'. Exception: {exception}"; + InnerWrite(errorMessage); + } } } @@ -168,15 +172,24 @@ void CalculateNewFileName(List logFiles, DateTime today) } } - var fileName = string.Format("nsb_log_{0}_{1}.txt", today.ToString("yyyy-MM-dd"), sequenceNumber); + var fileName = $"nsb_log_{today.ToString("yyyy-MM-dd")}_{sequenceNumber}.txt"; currentfilePath = Path.Combine(targetDirectory, fileName); } + protected string currentfilePath; + long currentFileSize; + internal Func GetDate = () => DateTime.Now.Date; + DateTime lastWriteDate; + long maxFileSize; + int numberOfArchiveFilesToKeep; + string targetDirectory; + const long fileLimitInBytes = 10L*1024*1024; //10MB + internal class LogFile { public DateTime DatePart; - public int SequenceNumber; public string Path; + public int SequenceNumber; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Logging/SetLoggingLibrary_Obsolete.cs b/src/NServiceBus.Core/Logging/SetLoggingLibrary_Obsolete.cs deleted file mode 100644 index 792a4b2678b..00000000000 --- a/src/NServiceBus.Core/Logging/SetLoggingLibrary_Obsolete.cs +++ /dev/null @@ -1,89 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using Logging; - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net and Nlog integration has been moved to a stand alone nugets, 'NServiceBus.Log4Net' and 'NServiceBus.NLog'.")] - public static class SetLoggingLibrary - { - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net integration has been moved to a stand alone nuget 'NServiceBus.Log4Net'. Install the 'NServiceBus.Log4Net' nuget and run 'LogManager.Use();'.")] - public static Configure Log4Net(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net integration has been moved to a stand alone nuget 'NServiceBus.Log4Net'. Install the 'NServiceBus.Log4Net' nuget and run 'LogManager.Use();'.")] - public static Configure Log4Net(this Configure config, Action initializeAppender) where TAppender : new() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net integration has been moved to a stand alone nuget 'NServiceBus.Log4Net'. Install the 'NServiceBus.Log4Net' nuget and run 'LogManager.Use();'.")] - public static Configure Log4Net(this Configure config, object appenderSkeleton) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net integration has been moved to a stand alone nuget 'NServiceBus.Log4Net'. Install the 'NServiceBus.Log4Net' nuget and run 'LogManager.Use();'.")] - public static void Log4Net() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Log4Net integration has been moved to a stand alone nuget 'NServiceBus.Log4Net'. Install the 'NServiceBus.Log4Net' nuget and run 'LogManager.Use();'.")] - public static void Log4Net(Action config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Nlog integration has been moved to a stand alone nuget 'NServiceBus.NLog'. Install the 'NServiceBus.NLog' nuget and run 'LogManager.Use();'.")] - public static Configure NLog(this Configure config, params object[] targetsForNServiceBusToLogTo) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Message = "Nlog integration has been moved to a stand alone nuget 'NServiceBus.NLog'. Install the 'NServiceBus.NLog' nuget and run 'LogManager.Use();'.")] - public static void NLog() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - Replacement = "LogManager.UseFactory(ILoggerFactory)")] - public static void Custom(ILoggerFactory loggerFactory) - { - throw new NotImplementedException(); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/LogicalAddress.cs b/src/NServiceBus.Core/LogicalAddress.cs new file mode 100644 index 00000000000..7293162f26a --- /dev/null +++ b/src/NServiceBus.Core/LogicalAddress.cs @@ -0,0 +1,154 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Routing; + + /// + /// Represents a logical address (independent of transport). + /// + public struct LogicalAddress + { + LogicalAddress(EndpointInstance endpointInstance, string qualifier) + { + EndpointInstance = endpointInstance; + Qualifier = qualifier; + } + + /// + /// Creates a logical address for a remote endpoint. + /// + /// The endpoint instance that describes the remote endpoint. + public static LogicalAddress CreateRemoteAddress(EndpointInstance endpointInstance) + { + return new LogicalAddress(endpointInstance, null); + } + + /// + /// Creates a logical address for this endpoint. + /// + /// The name of the main input queue. + /// The additional transport-specific properties. + public static LogicalAddress CreateLocalAddress(string queueName, IReadOnlyDictionary properties) + { + return new LogicalAddress(new EndpointInstance(queueName, null, properties), null); + } + + /// + /// Creates a new logical address with the given qualifier. + /// + /// The qualifier for the new address. + public LogicalAddress CreateQualifiedAddress(string qualifier) + { + Guard.AgainstNullAndEmpty(nameof(qualifier), qualifier); + if (Qualifier != null) + { + throw new Exception("Cannot add a qualifier to an already qualified address."); + } + if (EndpointInstance.Discriminator != null) + { + throw new Exception("Cannot add a qualifier to an individualized address."); + } + return new LogicalAddress(EndpointInstance, qualifier); + } + + + /// + /// Creates a new individualized logical address with the specified discriminator. + /// + /// The discriminator value used to individualize the address. + public LogicalAddress CreateIndividualizedAddress(string discriminator) + { + Guard.AgainstNullAndEmpty(nameof(discriminator), discriminator); + if (EndpointInstance.Discriminator != null) + { + throw new Exception("Cannot add a discriminator to an already individualized address."); + } + if (Qualifier != null) + { + throw new Exception("Cannot add a discriminator to a qualified address."); + } + return new LogicalAddress(new EndpointInstance(EndpointInstance.Endpoint, discriminator, EndpointInstance.Properties), null); + } + + /// + /// Returns the qualifier, or null if the address isn't qualified. + /// + public string Qualifier { get; } + + /// + /// Returns the endpoint instance. + /// + public EndpointInstance EndpointInstance { get; } + + bool Equals(LogicalAddress other) + { + return string.Equals(Qualifier, other.Qualifier) && Equals(EndpointInstance, other.EndpointInstance); + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + if (Qualifier != null) + { + return EndpointInstance + "." + Qualifier; + } + return EndpointInstance.ToString(); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + return obj is LogicalAddress && Equals((LogicalAddress) obj); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + return ((Qualifier?.GetHashCode() ?? 0)*397) ^ (EndpointInstance?.GetHashCode() ?? 0); + } + } + + /// + /// Checks for equality. + /// + public static bool operator ==(LogicalAddress left, LogicalAddress right) + { + return Equals(left, right); + } + + /// + /// Checks for inequality. + /// + public static bool operator !=(LogicalAddress left, LogicalAddress right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageConventions_Obsolete.cs b/src/NServiceBus.Core/MessageConventions_Obsolete.cs deleted file mode 100644 index 48c84011792..00000000000 --- a/src/NServiceBus.Core/MessageConventions_Obsolete.cs +++ /dev/null @@ -1,84 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using System.Reflection; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningMessagesAs(definesMessageType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static class MessageConventions - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningMessagesAs(definesMessageType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningMessagesAs(this Configure config, Func definesMessageType) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningCommandsAs(definesCommandType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningCommandsAs(this Configure config, Func definesCommandType) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningEventsAs(definesEventType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningEventsAs(this Configure config, Func definesEventType) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningEncryptedPropertiesAs(definesEncryptedProperty)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningEncryptedPropertiesAs(this Configure config, Func definesEncryptedProperty) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningDataBusPropertiesAs(definesDataBusProperty)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningDataBusPropertiesAs(this Configure config, Func definesDataBusProperty) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningTimeToBeReceivedAs(retrieveTimeToBeReceived)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningTimeToBeReceivedAs(this Configure config, Func retrieveTimeToBeReceived) - { - throw new NotImplementedException(); - - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.Conventions().DefiningExpressMessagesAs(definesExpressMessageType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure DefiningExpressMessagesAs(this Configure config, Func definesExpressMessageType) - { - throw new NotImplementedException(); - } - } - -} diff --git a/src/NServiceBus.Core/MessageDeserializationException.cs b/src/NServiceBus.Core/MessageDeserializationException.cs index 0c7b454cd49..8d00a5b556f 100644 --- a/src/NServiceBus.Core/MessageDeserializationException.cs +++ b/src/NServiceBus.Core/MessageDeserializationException.cs @@ -2,27 +2,34 @@ { using System; using System.Runtime.Serialization; - using NServiceBus.Unicast.Messages; + using Pipeline; /// - /// Wraps the that occurs when the contents of a is deserialized to a list of s. + /// Wraps the that occurs when the contents of a is deserialized + /// to a list of s. /// [Serializable] public class MessageDeserializationException : SerializationException { /// - /// Initializes a new instance of . + /// Initializes a new instance of . + /// + public MessageDeserializationException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of . /// /// The exception that is the cause of the current exception. - /// The id of the that failed to deserialize. - public MessageDeserializationException(string transportMessageId, Exception innerException) + /// The id of the that failed to deserialize. + public MessageDeserializationException(string transportMessageId, Exception innerException) : base("An error occurred while attempting to extract logical messages from transport message " + transportMessageId, innerException) { - } /// - /// + /// . /// protected MessageDeserializationException(SerializationInfo info, StreamingContext context) : base(info, context) { diff --git a/src/NServiceBus.Core/MessageIntentEnum.cs b/src/NServiceBus.Core/MessageIntentEnum.cs index a8d6d3f28bc..560f1635f1f 100644 --- a/src/NServiceBus.Core/MessageIntentEnum.cs +++ b/src/NServiceBus.Core/MessageIntentEnum.cs @@ -1,34 +1,33 @@ namespace NServiceBus { - /// + /// /// Enumeration defining different kinds of message intent like Send and Publish. - /// + /// public enum MessageIntentEnum { - - /// - /// Regular point-to-point send - /// + /// + /// Regular point-to-point send. + /// Send = 1, - /// - /// Publish, not a regular point-to-point send - /// + /// + /// Publish, not a regular point-to-point send. + /// Publish = 2, /// - /// Subscribe + /// Subscribe. /// Subscribe = 3, /// - /// Unsubscribe + /// Unsubscribe. /// Unsubscribe = 4, /// - /// Indicates that this message is a reply + /// Indicates that this message is a reply. /// Reply = 5 } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageInterfaces/IMessageMapper.cs b/src/NServiceBus.Core/MessageInterfaces/IMessageMapper.cs index dffefc6c1c5..e58732385e1 100644 --- a/src/NServiceBus.Core/MessageInterfaces/IMessageMapper.cs +++ b/src/NServiceBus.Core/MessageInterfaces/IMessageMapper.cs @@ -25,4 +25,4 @@ public interface IMessageMapper : IMessageCreator /// Type GetMappedTypeFor(string typeName); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/ConcreteProxyCreator.cs b/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/ConcreteProxyCreator.cs index 0901afdcf96..e4043b51512 100644 --- a/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/ConcreteProxyCreator.cs +++ b/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/ConcreteProxyCreator.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.MessageInterfaces.MessageMapper.Reflection +namespace NServiceBus { using System; using System.Collections.Generic; @@ -9,9 +9,6 @@ namespace NServiceBus.MessageInterfaces.MessageMapper.Reflection class ConcreteProxyCreator { - internal const string SUFFIX = "__impl"; - ModuleBuilder moduleBuilder; - public ConcreteProxyCreator() { var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( @@ -75,7 +72,10 @@ public Type CreateTypeFrom(Type type) "set_" + prop.Name, getMethodBuilder.Attributes, null, - new[] { propertyType }); + new[] + { + propertyType + }); var setIL = setMethodBuilder.GetILGenerator(); // Load the instance and then the numeric argument, then store the @@ -115,7 +115,9 @@ static CustomAttributeBuilder BuildCustomAttribute(object customAttribute) // Get constructor with the largest number of parameters foreach (var cInfo in customAttribute.GetType().GetConstructors(). Where(cInfo => longestCtor == null || longestCtor.GetParameters().Length < cInfo.GetParameters().Length)) + { longestCtor = cInfo; + } if (longestCtor == null) { @@ -163,7 +165,7 @@ static CustomAttributeBuilder BuildCustomAttribute(object customAttribute) var defaultAttributes = attrPropInfo.GetCustomAttributes(typeof(DefaultValueAttribute), true); if (defaultAttributes.Length > 0) { - defaultValue = ((DefaultValueAttribute)defaultAttributes[0]).Value; + defaultValue = ((DefaultValueAttribute) defaultAttributes[0]).Value; } var value = attrPropInfo.GetValue(customAttribute, null); if (value == defaultValue) @@ -187,18 +189,18 @@ static IEnumerable GetAllProperties(Type type) props.AddRange(GetAllProperties(interfaceType)); } - var names = new List(props.Count); + var tracked = new List(props.Count); var duplicates = new List(props.Count); foreach (var p in props) { - var duplicate = names.SingleOrDefault(n => n.Name == p.Name && n.PropertyType == p.PropertyType); + var duplicate = tracked.SingleOrDefault(n => n.Name == p.Name && n.PropertyType == p.PropertyType); if (duplicate != null) { duplicates.Add(p); } else { - names.Add(p); + tracked.Add(p); } } @@ -210,5 +212,7 @@ static IEnumerable GetAllProperties(Type type) return props; } + ModuleBuilder moduleBuilder; + internal const string SUFFIX = "__impl"; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/MessageMapper.cs b/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/MessageMapper.cs index 3b97279b0ca..f8c8c36fc82 100644 --- a/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/MessageMapper.cs +++ b/src/NServiceBus.Core/MessageInterfaces/MessageMapper/Reflection/MessageMapper.cs @@ -2,23 +2,19 @@ namespace NServiceBus.MessageInterfaces.MessageMapper.Reflection { using System; using System.Collections; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; - using Logging; - using Utils.Reflection; /// /// Uses reflection to map between interfaces and their generated concrete implementations. /// public class MessageMapper : IMessageMapper { - - ConcreteProxyCreator concreteProxyCreator; - /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// public MessageMapper() { @@ -41,6 +37,104 @@ public void Initialize(IEnumerable types) } } + /// + /// If the given type is concrete, returns the interface it was generated to support. + /// If the given type is an interface, returns the concrete class generated to implement it. + /// + public Type GetMappedTypeFor(Type t) + { + Guard.AgainstNull(nameof(t), t); + RuntimeTypeHandle typeHandle; + if (t.IsClass) + { + if (t.IsGenericTypeDefinition) + { + return null; + } + + if (concreteToInterfaceTypeMapping.TryGetValue(t.TypeHandle, out typeHandle)) + { + return Type.GetTypeFromHandle(typeHandle); + } + + return t; + } + + if (interfaceToConcreteTypeMapping.TryGetValue(t.TypeHandle, out typeHandle)) + { + return Type.GetTypeFromHandle(typeHandle); + } + + return null; + } + + /// + /// Returns the type mapped to the given name. + /// + public Type GetMappedTypeFor(string typeName) + { + Guard.AgainstNullAndEmpty(nameof(typeName), typeName); + var name = typeName; + if (typeName.EndsWith(ConcreteProxyCreator.SUFFIX, StringComparison.Ordinal)) + { + name = typeName.Substring(0, typeName.Length - ConcreteProxyCreator.SUFFIX.Length); + } + + RuntimeTypeHandle typeHandle; + if (nameToType.TryGetValue(name, out typeHandle)) + { + return Type.GetTypeFromHandle(typeHandle); + } + + return Type.GetType(name); + } + + /// + /// Calls the generic CreateInstance and performs the given action on the result. + /// + public T CreateInstance(Action action) + { + var result = CreateInstance(); + + action?.Invoke(result); + + return result; + } + + /// + /// Calls the and returns its result cast to . + /// + public T CreateInstance() + { + return (T)CreateInstance(typeof(T)); + } + + /// + /// If the given type is an interface, finds its generated concrete implementation, instantiates it, and returns the + /// result. + /// + public object CreateInstance(Type t) + { + var mapped = t; + if (t.IsInterface || t.IsAbstract) + { + mapped = GetMappedTypeFor(t); + if (mapped == null) + { + InitType(t); + mapped = GetMappedTypeFor(t); + } + } + + RuntimeMethodHandle constructor; + if (typeToConstructor.TryGetValue(mapped.TypeHandle, out constructor)) + { + return ((ConstructorInfo)MethodBase.GetMethodFromHandle(constructor, mapped.TypeHandle)).Invoke(null); + } + + return FormatterServices.GetUninitializedObject(mapped); + } + /// /// Generates a concrete implementation of the given type if it is an interface. /// @@ -62,13 +156,15 @@ void InitType(Type t) foreach (var interfaceType in t.GetInterfaces()) { - foreach (var g in interfaceType.GetGenericArguments()) - { - if(g == t) - continue; - - InitType(g); - } + foreach (var g in interfaceType.GetGenericArguments()) + { + if (g == t) + { + continue; + } + + InitType(g); + } } return; @@ -76,23 +172,36 @@ void InitType(Type t) var typeName = GetTypeName(t); - //already handled this type, prevent infinite recursion - if (nameToType.ContainsKey(typeName)) + // check and proxy generation is not threadsafe + lock (messageInitializationLock) { - return; - } + //already handled this type, prevent infinite recursion + if (nameToType.ContainsKey(typeName)) + { + return; + } - if (t.IsInterface) - { - GenerateImplementationFor(t); + if (t.IsInterface) + { + GenerateImplementationFor(t); + } + else + { + var constructorInfo = t.GetConstructor(Type.EmptyTypes); + if (constructorInfo != null) + { + typeToConstructor[t.TypeHandle] = constructorInfo.MethodHandle; + } + } + + nameToType[typeName] = t.TypeHandle; } - else + + if (!t.IsInterface) { - typeToConstructor[t] = t.GetConstructor(Type.EmptyTypes); + return; } - nameToType[typeName] = t; - foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) { InitType(field.FieldType); @@ -108,19 +217,21 @@ void GenerateImplementationFor(Type interfaceType) { if (!interfaceType.IsVisible) { - throw new Exception(string.Format("We can only generate a concrete implementation for '{0}' if '{0}' is public.", interfaceType)); + throw new Exception($"Can only generate a concrete implementation for '{interfaceType}' if '{interfaceType}' is public."); } if (interfaceType.GetMethods().Any(mi => !(mi.IsSpecialName && (mi.Name.StartsWith("set_") || mi.Name.StartsWith("get_"))))) { - Logger.Warn(string.Format("Interface {0} contains methods and can there for not be mapped. Be aware that non mapped interface can't be used to send messages.",interfaceType.Name)); - return; + throw new Exception($"Can only generate a concrete implementation for '{interfaceType.Name}' because the interface contains methods. Ensure interface messages do not contain methods."); } - var mapped = concreteProxyCreator.CreateTypeFrom(interfaceType); - interfaceToConcreteTypeMapping[interfaceType] = mapped; - concreteToInterfaceTypeMapping[mapped] = interfaceType; - typeToConstructor[mapped] = mapped.GetConstructor(Type.EmptyTypes); + interfaceToConcreteTypeMapping[interfaceType.TypeHandle] = mapped.TypeHandle; + concreteToInterfaceTypeMapping[mapped.TypeHandle] = interfaceType.TypeHandle; + var constructorInfo = mapped.GetConstructor(Type.EmptyTypes); + if (constructorInfo != null) + { + typeToConstructor[mapped.TypeHandle] = constructorInfo.MethodHandle; + } } static string GetTypeName(Type t) @@ -137,101 +248,12 @@ static string GetTypeName(Type t) return t.FullName; } - /// - /// If the given type is concrete, returns the interface it was generated to support. - /// If the given type is an interface, returns the concrete class generated to implement it. - /// - public Type GetMappedTypeFor(Type t) - { - if (t.IsClass) - { - Type result; - concreteToInterfaceTypeMapping.TryGetValue(t, out result); - if (result != null || t.IsGenericTypeDefinition) - { - return result; - } - - return t; - } - - Type toReturn; - interfaceToConcreteTypeMapping.TryGetValue(t, out toReturn); - return toReturn; - } - - /// - /// Returns the type mapped to the given name. - /// - public Type GetMappedTypeFor(string typeName) - { - var name = typeName; - if (typeName.EndsWith(ConcreteProxyCreator.SUFFIX, StringComparison.Ordinal)) - { - name = typeName.Substring(0, typeName.Length - ConcreteProxyCreator.SUFFIX.Length); - } - - Type type; - if (nameToType.TryGetValue(name, out type)) - { - return type; - } - - return Type.GetType(name); - } - - /// - /// Calls the generic CreateInstance and performs the given action on the result. - /// - public T CreateInstance(Action action) - { - var result = CreateInstance(); - - if (action != null) - { - action(result); - } - - return result; - } - - /// - /// Calls the and returns its result cast to . - /// - public T CreateInstance() - { - return (T)CreateInstance(typeof(T)); - } - - /// - /// If the given type is an interface, finds its generated concrete implementation, instantiates it, and returns the result. - /// - public object CreateInstance(Type t) - { - var mapped = t; - if (t.IsInterface || t.IsAbstract) - { - mapped = GetMappedTypeFor(t); - if (mapped == null) - { - throw new ArgumentException("Could not find a concrete type mapped to " + t.FullName); - } - } - - ConstructorInfo constructor; - typeToConstructor.TryGetValue(mapped, out constructor); - if (constructor != null) - { - return constructor.Invoke(null); - } + readonly object messageInitializationLock = new object(); - return FormatterServices.GetUninitializedObject(mapped); - } - - Dictionary interfaceToConcreteTypeMapping = new Dictionary(); - Dictionary concreteToInterfaceTypeMapping = new Dictionary(); - Dictionary nameToType = new Dictionary(); - Dictionary typeToConstructor = new Dictionary(); - static ILog Logger = LogManager.GetLogger(); + ConcreteProxyCreator concreteProxyCreator; + ConcurrentDictionary concreteToInterfaceTypeMapping = new ConcurrentDictionary(); + ConcurrentDictionary interfaceToConcreteTypeMapping = new ConcurrentDictionary(); + ConcurrentDictionary nameToType = new ConcurrentDictionary(); + ConcurrentDictionary typeToConstructor = new ConcurrentDictionary(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/ApplyIncomingMessageMutatorsBehavior.cs b/src/NServiceBus.Core/MessageMutator/ApplyIncomingMessageMutatorsBehavior.cs deleted file mode 100644 index 24aada9acc1..00000000000 --- a/src/NServiceBus.Core/MessageMutator/ApplyIncomingMessageMutatorsBehavior.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.MessageMutator; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - - - class ApplyIncomingMessageMutatorsBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - var current = context.IncomingLogicalMessage.Instance; - - foreach (var mutator in context.Builder.BuildAll()) - { - //message mutators may need to assume that this has been set (eg. for the purposes of headers). - ExtensionMethods.CurrentMessageBeingHandled = current; - current = mutator.MutateIncoming(current); - context.IncomingLogicalMessage.UpdateMessageInstance(current); - } - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/ApplyIncomingTransportMessageMutatorsBehavior.cs b/src/NServiceBus.Core/MessageMutator/ApplyIncomingTransportMessageMutatorsBehavior.cs deleted file mode 100644 index 54e5a360814..00000000000 --- a/src/NServiceBus.Core/MessageMutator/ApplyIncomingTransportMessageMutatorsBehavior.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.MessageMutator; - using Pipeline; - using Pipeline.Contexts; - - - class ApplyIncomingTransportMessageMutatorsBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - var mutators = context.Builder.BuildAll(); - - foreach (var mutator in mutators) - { - mutator.MutateIncoming(context.PhysicalMessage); - } - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMessageMutator.cs b/src/NServiceBus.Core/MessageMutator/IMessageMutator.cs deleted file mode 100644 index f8e463e507c..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMessageMutator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - /// - /// Use this interface to change logical messages before any other code sees them. - /// - public interface IMessageMutator : IMutateOutgoingMessages, IMutateIncomingMessages{} -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMutateIncomingMessages.cs b/src/NServiceBus.Core/MessageMutator/IMutateIncomingMessages.cs deleted file mode 100644 index cd2939c0cbe..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMutateIncomingMessages.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - /// - /// Mutates incoming messages - /// - public interface IMutateIncomingMessages - { - /// - /// Mutates the given message right after it has been deserialized - /// - object MutateIncoming(object message); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMutateIncomingTransportMessages.cs b/src/NServiceBus.Core/MessageMutator/IMutateIncomingTransportMessages.cs deleted file mode 100644 index e6f21fab9bc..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMutateIncomingTransportMessages.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - /// - /// Mutates transport messages when they are received. - /// Implementors are invoked before the logical messages have been deserialized. - /// - public interface IMutateIncomingTransportMessages - { - /// - /// Modifies various properties of the transport message. - /// - void MutateIncoming(TransportMessage transportMessage); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMutateOutgoingMessages.cs b/src/NServiceBus.Core/MessageMutator/IMutateOutgoingMessages.cs deleted file mode 100644 index 15dba6246e2..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMutateOutgoingMessages.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - /// - /// Mutates outgoing messages - /// - public interface IMutateOutgoingMessages - { - /// - /// Mutates the given message just before it's serialized - /// - object MutateOutgoing(object message); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMutateOutgoingTransportMessages.cs b/src/NServiceBus.Core/MessageMutator/IMutateOutgoingTransportMessages.cs deleted file mode 100644 index 64c674d9214..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMutateOutgoingTransportMessages.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - using Unicast.Messages; - - /// - /// Mutates transport messages before they are sent. - /// Implementors are invoked after the logical messages have been serialized. - /// - public interface IMutateOutgoingTransportMessages - { - /// - /// Modifies various properties of the transport message. - /// - /// Mutations should be applied to the . - /// The outgoing that wraps the actual business message. See to get the actual business message. - /// The physical message about to be sent to the queue. - void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/IMutateTransportMessages.cs b/src/NServiceBus.Core/MessageMutator/IMutateTransportMessages.cs deleted file mode 100644 index 661f91e953e..00000000000 --- a/src/NServiceBus.Core/MessageMutator/IMutateTransportMessages.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.MessageMutator -{ - /// - /// Use this interface to change transport messages before any other code sees them. - /// - public interface IMutateTransportMessages : IMutateIncomingTransportMessages, IMutateOutgoingTransportMessages {} -} diff --git a/src/NServiceBus.Core/MessageMutator/MutateOutgoingMessageBehavior.cs b/src/NServiceBus.Core/MessageMutator/MutateOutgoingMessageBehavior.cs deleted file mode 100644 index ea5d030c90b..00000000000 --- a/src/NServiceBus.Core/MessageMutator/MutateOutgoingMessageBehavior.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.MessageMutator; - using Pipeline; - using Pipeline.Contexts; - using Unicast.Transport; - - - class MutateOutgoingMessageBehavior : IBehavior - { - public void Invoke(OutgoingContext context, Action next) - { - if (context.OutgoingLogicalMessage.IsControlMessage()) - { - next(); - return; - } - - var currentMessageToSend = context.OutgoingLogicalMessage.Instance; - - foreach (var mutator in context.Builder.BuildAll()) - { - currentMessageToSend = mutator.MutateOutgoing(currentMessageToSend); - context.OutgoingLogicalMessage.UpdateMessageInstance(currentMessageToSend); - } - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageMutator/MutateOutgoingPhysicalMessageBehavior.cs b/src/NServiceBus.Core/MessageMutator/MutateOutgoingPhysicalMessageBehavior.cs deleted file mode 100644 index 8226c532ed1..00000000000 --- a/src/NServiceBus.Core/MessageMutator/MutateOutgoingPhysicalMessageBehavior.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.MessageMutator; - using Pipeline; - using Pipeline.Contexts; - - class MutateOutgoingPhysicalMessageBehavior : IBehavior - { - public void Invoke(OutgoingContext context, Action next) - { - foreach (var mutator in context.Builder.BuildAll()) - { - mutator.MutateOutgoing(context.OutgoingLogicalMessage, context.OutgoingMessage); - } - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/MessageSession.cs b/src/NServiceBus.Core/MessageSession.cs new file mode 100644 index 00000000000..64134ce5c3d --- /dev/null +++ b/src/NServiceBus.Core/MessageSession.cs @@ -0,0 +1,45 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + class MessageSession : IMessageSession + { + public MessageSession(RootContext context) + { + this.context = context; + } + + public Task Send(object message, SendOptions options) + { + return MessageOperations.Send(context, message, options); + } + + public Task Send(Action messageConstructor, SendOptions options) + { + return MessageOperations.Send(context, messageConstructor, options); + } + + public Task Publish(object message, PublishOptions options) + { + return MessageOperations.Publish(context, message, options); + } + + public Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + return MessageOperations.Publish(context, messageConstructor, publishOptions); + } + + public Task Subscribe(Type eventType, SubscribeOptions options) + { + return MessageOperations.Subscribe(context, eventType, options); + } + + public Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + return MessageOperations.Unsubscribe(context, eventType, options); + } + + RootContext context; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeBehavior.cs b/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeBehavior.cs deleted file mode 100644 index 185acdad7c6..00000000000 --- a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeBehavior.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - - - class CriticalTimeBehavior : IBehavior - { - CriticalTimeCalculator criticalTimeCounter; - - public CriticalTimeBehavior(CriticalTimeCalculator criticalTimeCounter) - { - this.criticalTimeCounter = criticalTimeCounter; - } - - public void Invoke(IncomingContext context, Action next) - { - next(); - - DateTime timeSent; - - if (!context.TryGet("IncomingMessage.TimeSent", out timeSent)) - { - return; - } - - criticalTimeCounter.Update(timeSent, context.Get("IncomingMessage.ProcessingStarted"), context.Get("IncomingMessage.ProcessingEnded")); - } - - public class Registration : RegisterStep - { - public Registration() - : base("CriticalTime", typeof(CriticalTimeBehavior), "Updates the critical time performance counter") - { - InsertBefore(WellKnownStep.ProcessingStatistics); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeCalculator.cs b/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeCalculator.cs deleted file mode 100644 index 1a494bfc23d..00000000000 --- a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeCalculator.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Diagnostics; - using System.Threading; - - /// - /// Performance counter for the critical time - /// - class CriticalTimeCalculator : IDisposable - { - PerformanceCounter counter; - TimeSpan estimatedMaximumProcessingDuration = TimeSpan.FromSeconds(2); - DateTime lastMessageProcessedTime; -// ReSharper disable once NotAccessedField.Local - Timer timer; - - public CriticalTimeCalculator(PerformanceCounter cnt) - { - counter = cnt; - timer = new Timer(ResetCounterValueIfNoMessageHasBeenProcessedRecently, null, 0, 2000); - } - - public void Dispose() - { - //Injected at compile time - } - - public void Update(DateTime sentInstant, DateTime processingStartedInstant, DateTime processingEndedInstant) - { - var endToEndTime = processingEndedInstant - sentInstant; - counter.RawValue = Convert.ToInt32(endToEndTime.TotalSeconds); - - lastMessageProcessedTime = processingEndedInstant; - - var processingDuration = processingEndedInstant - processingStartedInstant; - estimatedMaximumProcessingDuration = processingDuration.Add(TimeSpan.FromSeconds(1)); - } - - void ResetCounterValueIfNoMessageHasBeenProcessedRecently(object state) - { - if (NoMessageHasBeenProcessedRecently()) - { - counter.RawValue = 0; - } - } - - bool NoMessageHasBeenProcessedRecently() - { - var timeFromLastMessageProcessed = DateTime.UtcNow - lastMessageProcessedTime; - return timeFromLastMessageProcessed > estimatedMaximumProcessingDuration; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoring.cs b/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoring.cs deleted file mode 100644 index 8762de62773..00000000000 --- a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoring.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Features -{ - /// - /// Used to configure CriticalTimeMonitoring. - /// - public class CriticalTimeMonitoring : Feature - { - internal CriticalTimeMonitoring() - { - } - - /// - /// - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - var criticalTimeCounter = PerformanceCounterHelper.InstantiatePerformanceCounter("Critical Time", context.Settings.EndpointName()); - var criticalTimeCalculator = new CriticalTimeCalculator(criticalTimeCounter); - context.Container.RegisterSingleton(criticalTimeCalculator); - context.Pipeline.Register(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoringConfig.cs b/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoringConfig.cs deleted file mode 100644 index 5a73ac3723a..00000000000 --- a/src/NServiceBus.Core/Monitoring/CriticalTime/CriticalTimeMonitoringConfig.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus -{ - using NServiceBus.Features; - - /// - /// Provide configuration options for monitoring related settings. - /// - public static class CriticalTimeMonitoringConfig - { - - /// - /// Enables the NServiceBus specific performance counters. - /// - public static void EnableCriticalTimePerformanceCounter(this BusConfiguration config) - { - config.EnableFeature(); - } - } -} diff --git a/src/NServiceBus.Core/Monitoring/MonitoringConfig_Obsolete.cs b/src/NServiceBus.Core/Monitoring/MonitoringConfig_Obsolete.cs deleted file mode 100644 index 4803779eed3..00000000000 --- a/src/NServiceBus.Core/Monitoring/MonitoringConfig_Obsolete.cs +++ /dev/null @@ -1,35 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - Message = "Use `configuration.EnableCriticalTimePerformanceCounter()` or `configuration.EnableSLAPerformanceCounter(TimeSpan)`, where configuration is an instance of type `BusConfiguration`.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class MonitoringConfig - { - [ObsoleteEx( - Message = "Use `configuration.EnableSLAPerformanceCounter(TimeSpan)`, where configuration is an instance of type `BusConfiguration`.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure SetEndpointSLA(this Configure config, TimeSpan sla) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - Message = "Use `configuration.EnableCriticalTimePerformanceCounter()`, where configuration is an instance of type `BusConfiguration`.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure EnablePerformanceCounters(this Configure config) - { - throw new NotImplementedException(); - } - - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/PerformanceCounterHelper.cs b/src/NServiceBus.Core/Monitoring/PerformanceCounterHelper.cs deleted file mode 100644 index a7acd647994..00000000000 --- a/src/NServiceBus.Core/Monitoring/PerformanceCounterHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Diagnostics; - using NServiceBus.Logging; - - static class PerformanceCounterHelper - { - static ILog logger = LogManager.GetLogger(typeof(PerformanceCounterHelper)); - - public static PerformanceCounter InstantiatePerformanceCounter(string counterName, string instanceName) - { - PerformanceCounter counter; - - TryToInstantiatePerformanceCounter(counterName, instanceName, out counter, true); - - return counter; - } - - public static bool TryToInstantiatePerformanceCounter(string counterName, string instanceName, out PerformanceCounter counter) - { - return TryToInstantiatePerformanceCounter(counterName, instanceName, out counter, false); - } - - static bool TryToInstantiatePerformanceCounter(string counterName, string instanceName, out PerformanceCounter counter, bool throwIfFails) - { - if (instanceName.Length > 128) - { - throw new Exception(string.Format("The endpoint name ('{0}') is too long (longer then {1}) to register as a performance counter instance name. Please reduce the endpoint name.", instanceName, (int)SByte.MaxValue)); - } - - var message = String.Format("NServiceBus performance counter for '{0}' is not set up correctly. To rectify this problem see http://docs.particular.net/search?q=PerformanceCounters.", counterName); - - try - { - counter = new PerformanceCounter("NServiceBus", counterName, instanceName, false); - } - catch (Exception ex) - { - if (throwIfFails) - { - throw new InvalidOperationException(message, ex); - } - - logger.Info(message); - counter = null; - - return false; - } - - return true; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/PerformanceMonitorUsersInstaller.cs b/src/NServiceBus.Core/Monitoring/PerformanceMonitorUsersInstaller.cs deleted file mode 100644 index d1038a39bfa..00000000000 --- a/src/NServiceBus.Core/Monitoring/PerformanceMonitorUsersInstaller.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Security.Principal; - using Logging; - using NServiceBus.Installation; - - /// - /// Add the identity to the 'Performance Monitor Users' local group - /// - class PerformanceMonitorUsersInstaller : INeedToInstallSomething - { - static ILog logger = LogManager.GetLogger(); - static string builtinPerformanceMonitoringUsersName; - - static PerformanceMonitorUsersInstaller() - { - builtinPerformanceMonitoringUsersName = new SecurityIdentifier(WellKnownSidType.BuiltinPerformanceMonitoringUsersSid, null).Translate(typeof(NTAccount)).ToString(); - var parts = builtinPerformanceMonitoringUsersName.Split('\\'); - - if (parts.Length == 2) - { - builtinPerformanceMonitoringUsersName = parts[1]; - } - } - public void Install(string identity, Configure config) - { - //did not use DirectoryEntry to avoid a ref to the DirectoryServices.dll - try - { - if (!ElevateChecker.IsCurrentUserElevated()) - { - logger.InfoFormat(@"Did not attempt to add user '{0}' to group '{1}' since process is not running with elevate privileges. Processing will continue. To manually perform this action run the following command from an admin console: -net localgroup ""{1}"" ""{0}"" /add", identity, builtinPerformanceMonitoringUsersName); - return; - } - StartProcess(identity); - } - catch (Exception win32Exception) - { - var message = string.Format( - @"Failed adding user '{0}' to group '{1}' due to an Exception. -To help diagnose the problem try running the following command from an admin console: -net localgroup ""{1}"" ""{0}"" /add", identity, builtinPerformanceMonitoringUsersName); - logger.Warn(message, win32Exception); - } - } - - - void StartProcess(string identity) - { - //net localgroup "Performance Monitor Users" "{user account}" /add - var startInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardError = true, - Arguments = string.Format("localgroup \"{1}\" \"{0}\" /add", identity, builtinPerformanceMonitoringUsersName), - FileName = "net", - WorkingDirectory = Path.GetTempPath() - }; - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(5000); - - if (process.ExitCode == 0) - { - logger.Info(string.Format("Added user '{0}' to group '{1}'.", identity, builtinPerformanceMonitoringUsersName)); - return; - } - var error = process.StandardError.ReadToEnd(); - if (IsAlreadyAMemberError(error)) - { - logger.Info(string.Format("Skipped adding user '{0}' to group '{1}' because the user is already in group.", identity, builtinPerformanceMonitoringUsersName)); - return; - } - if (IsGroupDoesNotExistError(error)) - { - logger.Info(string.Format("Skipped adding user '{0}' to group '{1}' because the group does not exist.", identity, builtinPerformanceMonitoringUsersName)); - return; - } - var message = string.Format( - @"Failed to add user '{0}' to group '{2}'. -Error: {1} -To help diagnose the problem try running the following command from an admin console: -net localgroup ""{2}"" ""{0}"" /add", identity, error, builtinPerformanceMonitoringUsersName); - logger.Info(message); - } - } - - bool IsAlreadyAMemberError(string error) - { - return error.Contains("1378"); - } - - bool IsGroupDoesNotExistError(string error) - { - //required since 'Performance Monitor Users' does not exist on all windows OS. - return error.Contains("1376"); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/ProcessingStatisticsBehavior.cs b/src/NServiceBus.Core/Monitoring/ProcessingStatisticsBehavior.cs deleted file mode 100644 index bb2d0e6fed7..00000000000 --- a/src/NServiceBus.Core/Monitoring/ProcessingStatisticsBehavior.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - - class ProcessingStatisticsBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - string timeSentString; - var headers = context.PhysicalMessage.Headers; - - if (headers.TryGetValue(Headers.TimeSent, out timeSentString)) - { - context.Set("IncomingMessage.TimeSent", DateTimeExtensions.ToUtcDateTime(timeSentString)); - } - - context.Set("IncomingMessage.ProcessingStarted", DateTime.UtcNow); - - try - { - next(); - } - finally - { - context.Set("IncomingMessage.ProcessingEnded", DateTime.UtcNow); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/SLA/EndpointSLAAttribute.cs b/src/NServiceBus.Core/Monitoring/SLA/EndpointSLAAttribute.cs deleted file mode 100644 index 620dadfca54..00000000000 --- a/src/NServiceBus.Core/Monitoring/SLA/EndpointSLAAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Defines the SLA for this endpoint. Needs to be set on the endpoint configuration class - /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class EndpointSLAAttribute : Attribute - { - /// - /// Used to define the SLA for this endpoint - /// - /// A representing a - public EndpointSLAAttribute(string sla) - { - TimeSpan timespan; - if (!TimeSpan.TryParse(sla, out timespan)) - { - throw new InvalidOperationException("A invalid SLA string has been defined - " + sla); - } - if (timespan <= TimeSpan.Zero) - { - throw new InvalidOperationException("A invalid SLA string has been defined. It must be a positive timespan. - " + sla); - } - SLA = timespan; - } - - /// - /// The SLA of the endpoint. - /// - public TimeSpan SLA { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/SLA/EstimatedTimeToSLABreachCalculator.cs b/src/NServiceBus.Core/Monitoring/SLA/EstimatedTimeToSLABreachCalculator.cs deleted file mode 100644 index e36c378214a..00000000000 --- a/src/NServiceBus.Core/Monitoring/SLA/EstimatedTimeToSLABreachCalculator.cs +++ /dev/null @@ -1,138 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; - - class EstimatedTimeToSLABreachCalculator : IDisposable - { - public EstimatedTimeToSLABreachCalculator(TimeSpan sla, PerformanceCounter slaBreachCounter) - { - endpointSLA = sla; - counter = slaBreachCounter; - - timer = new Timer(RemoveOldDataPoints, null, 0, 2000); - } - - public void Dispose() - { - //Injected at compile time - } - - public void Update(DateTime sent, DateTime processingStarted, DateTime processingEnded) - { - var dataPoint = new DataPoint - { - CriticalTime = processingEnded - sent, - ProcessingTime = processingEnded - processingStarted, - OccurredAt = processingEnded - }; - - lock (dataPoints) - { - dataPoints.Add(dataPoint); - if (dataPoints.Count > MaxDataPoints) - { - dataPoints.RemoveRange(0, dataPoints.Count - MaxDataPoints); - } - } - - UpdateTimeToSLABreach(); - } - - void UpdateTimeToSLABreach() - { - IList snapshots; - - lock (dataPoints) - { - snapshots = new List(dataPoints); - } - - var secondsToSLABreach = CalculateTimeToSLABreach(snapshots); - - counter.RawValue = Convert.ToInt32(Math.Min(secondsToSLABreach, Int32.MaxValue)); - } - - double CalculateTimeToSLABreach(IList snapshots) - { - //need at least 2 data points to be able to calculate - if (snapshots.Count < 2) - { - return double.MaxValue; - } - - DataPoint previous = null; - - var criticalTimeDelta = TimeSpan.Zero; - - foreach (var current in snapshots) - { - if (previous != null) - { - criticalTimeDelta += current.CriticalTime - previous.CriticalTime; - } - - previous = current; - } - - if (criticalTimeDelta.TotalSeconds <= 0.0) - { - return double.MaxValue; - } - - var elapsedTime = snapshots.Last().OccurredAt - snapshots.First().OccurredAt; - - if (elapsedTime.TotalSeconds <= 0.0) - { - return double.MaxValue; - } - - var lastKnownCriticalTime = snapshots.Last().CriticalTime.TotalSeconds; - - var criticalTimeDeltaPerSecond = criticalTimeDelta.TotalSeconds/elapsedTime.TotalSeconds; - - var secondsToSLABreach = (endpointSLA.TotalSeconds - lastKnownCriticalTime)/criticalTimeDeltaPerSecond; - - if (secondsToSLABreach < 0.0) - { - return 0.0; - } - - return secondsToSLABreach; - } - - void RemoveOldDataPoints(object state) - { - lock (dataPoints) - { - var last = dataPoints.LastOrDefault(); - - if (last != null) - { - var oldestDataToKeep = DateTime.UtcNow - new TimeSpan(last.ProcessingTime.Ticks*3); - - dataPoints.RemoveAll(d => d.OccurredAt < oldestDataToKeep); - } - } - - UpdateTimeToSLABreach(); - } - - const int MaxDataPoints = 10; - List dataPoints = new List(); - PerformanceCounter counter; - TimeSpan endpointSLA; -// ReSharper disable once NotAccessedField.Local - Timer timer; - - class DataPoint - { - public TimeSpan CriticalTime { get; set; } - public DateTime OccurredAt { get; set; } - public TimeSpan ProcessingTime { get; set; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/SLA/SLABehavior.cs b/src/NServiceBus.Core/Monitoring/SLA/SLABehavior.cs deleted file mode 100644 index 20a0007a930..00000000000 --- a/src/NServiceBus.Core/Monitoring/SLA/SLABehavior.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - - class SLABehavior : IBehavior - { - EstimatedTimeToSLABreachCalculator breachCalculator; - - public SLABehavior(EstimatedTimeToSLABreachCalculator breachCalculator) - { - this.breachCalculator = breachCalculator; - } - - public void Invoke(IncomingContext context, Action next) - { - next(); - - DateTime timeSent; - - if (!context.TryGet("IncomingMessage.TimeSent", out timeSent)) - { - return; - } - - breachCalculator.Update(timeSent, context.Get("IncomingMessage.ProcessingStarted"), context.Get("IncomingMessage.ProcessingEnded")); - } - - public class Registration:RegisterStep - { - public Registration() - : base("SLA", typeof(SLABehavior), "Updates the SLA performance counter") - { - InsertBefore(WellKnownStep.ProcessingStatistics); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoring.cs b/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoring.cs deleted file mode 100644 index 862fd645a4a..00000000000 --- a/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoring.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using System.Linq; - - /// - /// Used to configure SLAMonitoring. - /// - public class SLAMonitoring : Feature - { - internal const string EndpointSLAKey = "EndpointSLA"; - - internal SLAMonitoring() - { - } - - /// - /// - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - if (context.Settings.GetOrDefault("Endpoint.SendOnly")) - { - return; - } - - TimeSpan endpointSla; - if (!TryGetSLA(context, out endpointSla)) - { - return; - } - - var slaBreachCounter = PerformanceCounterHelper.InstantiatePerformanceCounter("SLA violation countdown", context.Settings.EndpointName()); - var timeToSLABreachCalculator = new EstimatedTimeToSLABreachCalculator(endpointSla, slaBreachCounter); - context.Container.RegisterSingleton(timeToSLABreachCalculator); - context.Pipeline.Register(); - } - - static bool TryGetSLA(FeatureConfigurationContext context, out TimeSpan endpointSla) - { - if (context.Settings.TryGet(EndpointSLAKey, out endpointSla)) - { - return true; - } - - if (TryGetSlaFromAttribute(context, out endpointSla)) - { - return true; - } - - return false; - } - - static bool TryGetSlaFromAttribute(FeatureConfigurationContext config, out TimeSpan sla) - { - var configType = config.Settings - .GetAvailableTypes() - .SingleOrDefault(t => typeof(IConfigureThisEndpoint).IsAssignableFrom(t) && !t.IsInterface); - - if (configType == null) - { - sla = TimeSpan.Zero; - return false; - } - - var endpointSLAAttribute = (EndpointSLAAttribute)configType.GetCustomAttributes(typeof(EndpointSLAAttribute), false) - .FirstOrDefault(); - if (endpointSLAAttribute == null) - { - sla = TimeSpan.Zero; - return false; - } - - sla = endpointSLAAttribute.SLA; - return true; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoringConfig.cs b/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoringConfig.cs deleted file mode 100644 index ecb91cea6d9..00000000000 --- a/src/NServiceBus.Core/Monitoring/SLA/SLAMonitoringConfig.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Features; - - /// - /// Provide configuration options for monitoring related settings. - /// - public static class SLAMonitoringConfig - { - /// - /// Enables the NServiceBus specific performance counters with a specific EndpointSLA. - /// - public static void EnableSLAPerformanceCounter(this BusConfiguration config, TimeSpan sla) - { - config.Settings.Set(SLAMonitoring.EndpointSLAKey, sla); - EnableSLAPerformanceCounter(config); - } - /// - /// Enables the NServiceBus specific performance counters with a specific EndpointSLA. - /// - public static void EnableSLAPerformanceCounter(this BusConfiguration config) - { - config.EnableFeature(); - } - } -} diff --git a/src/NServiceBus.Core/NServiceBus.Core.csproj b/src/NServiceBus.Core/NServiceBus.Core.csproj index 7cbf274954d..41beed7a8fa 100644 --- a/src/NServiceBus.Core/NServiceBus.Core.csproj +++ b/src/NServiceBus.Core/NServiceBus.Core.csproj @@ -1,5 +1,6 @@  - + + Debug @@ -9,7 +10,7 @@ Properties NServiceBus NServiceBus.Core - v4.5 + v4.5.2 512 true ..\NServiceBus.snk @@ -46,19 +47,23 @@ Designer + + Designer + ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - ..\packages\Janitor.Fody.1.1.6.0\lib\dotnet\Janitor.dll + + ..\packages\Janitor.Fody.1.2.0.0\lib\dotnet\Janitor.dll False - - ..\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + True - - ..\packages\Obsolete.Fody.3.1.0.0\lib\NET35\Obsolete.dll + + ..\packages\Obsolete.Fody.4.1.0\lib\dotnet\Obsolete.dll False @@ -88,190 +93,475 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + - - + - + + + + + + + + + + + + + - - - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + - - - - - - - - + + + + + - - + - - + - + - + - - - - - - - - - - - + + + + + - - - + - - - + - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - + + + + + - - - - - - + + - + + + + + + + - - + - - - + + @@ -285,185 +575,105 @@ - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - + + - - - - + - - - - - - - - - - - - - - - - - - - - + - - - + - - - - - - - + + - - - - - - + + + - - - + - - + - - + + - - - - + + + - - - - - - + @@ -471,45 +681,24 @@ - + - - - - - - - - - - + - - - - - - - - - - - - @@ -519,98 +708,49 @@ - - - - - + - - - - - - - - - + + + - - - - - - - - + + - - - - - - + + + + Form LicenseExpiredForm.cs - - - - - + - - - - - - - - - - - - - - + + - - - - + - - - - - - - - - + + + - - - - - - + - - + @@ -621,14 +761,13 @@ + - - Designer - + - + @@ -648,6 +787,10 @@ Designer + + + + @@ -663,11 +806,12 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + + - - + + \ No newline at end of file diff --git a/src/NServiceBus.Core/Notifications.cs b/src/NServiceBus.Core/Notifications.cs new file mode 100644 index 00000000000..a8c348d7757 --- /dev/null +++ b/src/NServiceBus.Core/Notifications.cs @@ -0,0 +1,20 @@ +// ReSharper disable ConvertToAutoProperty +// we need writable fields for disposing + +namespace NServiceBus +{ + using Faults; + + /// + /// Notifications. + /// + public partial class Notifications + { + /// + /// Push-based error notifications. + /// + public ErrorsNotifications Errors => errorNotifications; + + ErrorsNotifications errorNotifications = new ErrorsNotifications(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Notifications/EventAggregator.cs b/src/NServiceBus.Core/Notifications/EventAggregator.cs new file mode 100644 index 00000000000..e4528aa2b9c --- /dev/null +++ b/src/NServiceBus.Core/Notifications/EventAggregator.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using System.Linq; + using System.Threading.Tasks; + + class EventAggregator : IEventAggregator + { + public EventAggregator(NotificationSubscriptions subscriptions) + { + this.subscriptions = subscriptions; + } + + public Task Raise(T @event) + { + return Task.WhenAll(subscriptions.Get().Select(s => s.Invoke(@event))); + } + + NotificationSubscriptions subscriptions; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Notifications/EventAggregatorExtensions.cs b/src/NServiceBus.Core/Notifications/EventAggregatorExtensions.cs new file mode 100644 index 00000000000..c8d075fdb6a --- /dev/null +++ b/src/NServiceBus.Core/Notifications/EventAggregatorExtensions.cs @@ -0,0 +1,17 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + + static class EventAggregatorExtensions + { + + //note: This is provided as an extension method to keep the notifications internal for now. Once + // we make them public this will be moved to the IBehaviorContext interface + public static Task RaiseNotification(this IBehaviorContext context, T notification) + { + return context.Extensions.Get() + .Raise(notification); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Notifications/IEventAggregator.cs b/src/NServiceBus.Core/Notifications/IEventAggregator.cs new file mode 100644 index 00000000000..112bb4bd653 --- /dev/null +++ b/src/NServiceBus.Core/Notifications/IEventAggregator.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + + interface IEventAggregator + { + Task Raise(T @event); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Notifications/NotificationSubscriptions.cs b/src/NServiceBus.Core/Notifications/NotificationSubscriptions.cs new file mode 100644 index 00000000000..78935f2f892 --- /dev/null +++ b/src/NServiceBus.Core/Notifications/NotificationSubscriptions.cs @@ -0,0 +1,59 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + class NotificationSubscriptions + { + public IEnumerable Get() + { + List subscribers; + + if (!subscriptions.TryGetValue(typeof(T), out subscribers)) + { + return Enumerable.Empty(); + } + + return subscribers; + } + + public void Subscribe(Func subscription) + { + var eventType = typeof(T); + + List currentSubscriptions; + + if (!subscriptions.TryGetValue(eventType, out currentSubscriptions)) + { + currentSubscriptions = new List(); + subscriptions[eventType] = currentSubscriptions; + } + + currentSubscriptions.Add(new Subscription(subscription)); + } + + Dictionary> subscriptions = new Dictionary>(); + + public class Subscription : ISubscription + { + public Subscription(Func invocation) + { + this.invocation = invocation; + } + + public Task Invoke(object @event) + { + return invocation((T) @event); + } + + Func invocation; + } + + public interface ISubscription + { + Task Invoke(object @event); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/Autofac/AutofacObjectBuilder.cs b/src/NServiceBus.Core/ObjectBuilder/Autofac/AutofacObjectBuilder.cs index 5b66752c69c..535ce09bf2c 100644 --- a/src/NServiceBus.Core/ObjectBuilder/Autofac/AutofacObjectBuilder.cs +++ b/src/NServiceBus.Core/ObjectBuilder/Autofac/AutofacObjectBuilder.cs @@ -1,31 +1,21 @@ -namespace NServiceBus.ObjectBuilder.Autofac +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; - using global::Autofac; - using global::Autofac.Builder; - using global::Autofac.Core; - - /// - /// Autofac implementation of . - /// - class AutofacObjectBuilder : Common.IContainer - { - ILifetimeScope container; + using Autofac; + using Autofac.Builder; + using Autofac.Core; + using IContainer = ObjectBuilder.Common.IContainer; - /// - /// Instantiates the class utilizing the given container. - /// + class AutofacObjectBuilder : IContainer + { public AutofacObjectBuilder(ILifetimeScope container) { this.container = container ?? new ContainerBuilder().Build(); } - /// - /// Instantiates the class with an empty Autofac container. - /// public AutofacObjectBuilder() : this(null) { @@ -36,33 +26,28 @@ public void Dispose() //Injected at compile time } - /// - /// Returns a child instance of the container to facilitate deterministic disposal - /// of all resources built by the child container. - /// - public Common.IContainer BuildChildContainer() + public IContainer BuildChildContainer() { - return new AutofacObjectBuilder(container.BeginLifetimeScope()); + return new AutofacObjectBuilder(container.BeginLifetimeScope()) + { + isChild = true + }; } - /// - /// Build an instance of a given type using Autofac. - /// public object Build(Type typeToBuild) { return container.Resolve(typeToBuild); } - /// - /// Build all instances of a given type using Autofac. - /// public IEnumerable BuildAll(Type typeToBuild) { return ResolveAll(container, typeToBuild); } - void Common.IContainer.Configure(Type component, DependencyLifecycle dependencyLifecycle) + void IContainer.Configure(Type component, DependencyLifecycle dependencyLifecycle) { + ThrowIfCalledOnChildContainer(); + var registration = GetComponentRegistration(component); if (registration != null) @@ -79,8 +64,10 @@ void Common.IContainer.Configure(Type component, DependencyLifecycle dependencyL builder.Update(container.ComponentRegistry); } - void Common.IContainer.Configure(Func componentFactory, DependencyLifecycle dependencyLifecycle) + void IContainer.Configure(Func componentFactory, DependencyLifecycle dependencyLifecycle) { + ThrowIfCalledOnChildContainer(); + var registration = GetComponentRegistration(typeof(T)); if (registration != null) @@ -97,11 +84,10 @@ void Common.IContainer.Configure(Func componentFactory, DependencyLifecycl builder.Update(container.ComponentRegistry); } - /// - /// Configure the value of a named component property. - /// public void ConfigureProperty(Type component, string property, object value) { + ThrowIfCalledOnChildContainer(); + var registration = GetComponentRegistration(component); if (registration == null) @@ -113,11 +99,10 @@ public void ConfigureProperty(Type component, string property, object value) registration.Activating += (sender, e) => SetPropertyValue(e.Instance, property, value); } - /// - /// Register a singleton instance of a dependency within Autofac. - /// public void RegisterSingleton(Type lookupType, object instance) { + ThrowIfCalledOnChildContainer(); + var builder = new ContainerBuilder(); builder.RegisterInstance(instance).As(lookupType).PropertiesAutowired(); builder.Update(container.ComponentRegistry); @@ -132,9 +117,6 @@ public void Release(object instance) { } - /// - /// Set a property value on an instance using reflection - /// static void SetPropertyValue(object instance, string propertyName, object value) { instance.GetType() @@ -160,6 +142,14 @@ static void SetLifetimeScope(DependencyLifecycle dependencyLifecycle, IRegistrat } } + void ThrowIfCalledOnChildContainer() + { + if (isChild) + { + throw new InvalidOperationException("Reconfiguration of child containers is not allowed."); + } + } + IComponentRegistration GetComponentRegistration(Type concreteComponent) { return container.ComponentRegistry.Registrations.FirstOrDefault(x => x.Activator.LimitType == concreteComponent); @@ -173,9 +163,9 @@ static IEnumerable GetAllServices(Type type) } var result = new List(type.GetInterfaces()) - { - type - }; + { + type + }; foreach (var interfaceType in type.GetInterfaces()) { @@ -189,5 +179,8 @@ static IEnumerable ResolveAll(IComponentContext container, Type componen { return container.Resolve(typeof(IEnumerable<>).MakeGenericType(componentType)) as IEnumerable; } + + ILifetimeScope container; + bool isChild; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/Common/CommonObjectBuilder.cs b/src/NServiceBus.Core/ObjectBuilder/Common/CommonObjectBuilder.cs index b7114312ad2..acbcbc0c317 100644 --- a/src/NServiceBus.Core/ObjectBuilder/Common/CommonObjectBuilder.cs +++ b/src/NServiceBus.Core/ObjectBuilder/Common/CommonObjectBuilder.cs @@ -1,151 +1,104 @@ -namespace NServiceBus.ObjectBuilder.Common +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; - using System.Linq.Expressions; - using Utils.Reflection; + using ObjectBuilder; + using ObjectBuilder.Common; - /// - /// Implementation of IBuilder, serving as a facade that container specific implementations - /// of IContainer should run behind. - /// class CommonObjectBuilder : IBuilder, IConfigureComponents { - /// - /// The container that will be used to create objects and configure components. - /// - public IContainer Container { get; set; } - - /// - /// Used for multi-threaded rich clients to build and dispatch - /// in a synchronization domain. - /// - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0", - Message = "Smartclients should handle their own synchronization")] - public bool Synchronized + public CommonObjectBuilder(IContainer container) { - get { throw new NotImplementedException(); } - set { throw new NotImplementedException(); } - } - - public IComponentConfig ConfigureComponent(Type concreteComponent, DependencyLifecycle instanceLifecycle) - { - Container.Configure(concreteComponent, instanceLifecycle); - - return new ComponentConfig(concreteComponent, Container); + this.container = container; } - public IComponentConfig ConfigureComponent(DependencyLifecycle instanceLifecycle) + public IBuilder CreateChildBuilder() { - Container.Configure(typeof(T), instanceLifecycle); - - return new ComponentConfig(Container); + return new CommonObjectBuilder(container.BuildChildContainer()); } - public IComponentConfig ConfigureComponent(Func componentFactory, DependencyLifecycle instanceLifecycle) + public void Dispose() { - Container.Configure(componentFactory, instanceLifecycle); - - return new ComponentConfig(Container); + //Injected at compile time } - public IComponentConfig ConfigureComponent(Func componentFactory, DependencyLifecycle instanceLifecycle) + public T Build() { - Container.Configure(() => componentFactory(this), instanceLifecycle); - - return new ComponentConfig(Container); + return (T) container.Build(typeof(T)); } - public IConfigureComponents ConfigureProperty(Expression> property, object value) + public object Build(Type typeToBuild) { - var prop = Reflect.GetProperty(property); - - return ((IConfigureComponents)this).ConfigureProperty(prop.Name,value); + return container.Build(typeToBuild); } - public IConfigureComponents ConfigureProperty(string propertyName, object value) + IEnumerable IBuilder.BuildAll(Type typeToBuild) { - Container.ConfigureProperty(typeof(T), propertyName, value); - - return this; + return container.BuildAll(typeToBuild); } - IConfigureComponents IConfigureComponents.RegisterSingleton(Type lookupType, object instance) + void IBuilder.Release(object instance) { - Container.RegisterSingleton(lookupType, instance); - return this; + container.Release(instance); } - public IConfigureComponents RegisterSingleton(T instance) + public IEnumerable BuildAll() { - Container.RegisterSingleton(typeof(T), instance); - return this; + return container.BuildAll(typeof(T)).Cast(); } - public bool HasComponent() + public void BuildAndDispatch(Type typeToBuild, Action action) { - return Container.HasComponent(typeof(T)); + var o = container.Build(typeToBuild); + action(o); } - public bool HasComponent(Type componentType) + public void ConfigureComponent(Type concreteComponent, DependencyLifecycle instanceLifecycle) { - return Container.HasComponent(componentType); + container.Configure(concreteComponent, instanceLifecycle); } - - public IBuilder CreateChildBuilder() + public void ConfigureComponent(DependencyLifecycle instanceLifecycle) { - return new CommonObjectBuilder - { - Container = Container.BuildChildContainer() - }; + container.Configure(typeof(T), instanceLifecycle); } - public void Dispose() + public void ConfigureComponent(Func componentFactory, DependencyLifecycle instanceLifecycle) { - //Injected at compile time + container.Configure(componentFactory, instanceLifecycle); } - void DisposeManaged() + public void ConfigureComponent(Func componentFactory, DependencyLifecycle instanceLifecycle) { - if (Container != null) - { - Container.Dispose(); - } + container.Configure(() => componentFactory(this), instanceLifecycle); } - public T Build() + void IConfigureComponents.RegisterSingleton(Type lookupType, object instance) { - return (T)Container.Build(typeof(T)); + container.RegisterSingleton(lookupType, instance); } - public object Build(Type typeToBuild) + public void RegisterSingleton(T instance) { - return Container.Build(typeToBuild); + container.RegisterSingleton(typeof(T), instance); } - IEnumerable IBuilder.BuildAll(Type typeToBuild) + public bool HasComponent() { - return Container.BuildAll(typeToBuild); + return container.HasComponent(typeof(T)); } - void IBuilder.Release(object instance) + public bool HasComponent(Type componentType) { - Container.Release(instance); + return container.HasComponent(componentType); } - public IEnumerable BuildAll() + void DisposeManaged() { - return Container.BuildAll(typeof(T)).Cast(); + container?.Dispose(); } - public void BuildAndDispatch(Type typeToBuild, Action action) - { - var o = Container.Build(typeToBuild); - action(o); - } + IContainer container; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/Common/ComponentConfig.cs b/src/NServiceBus.Core/ObjectBuilder/Common/ComponentConfig.cs deleted file mode 100644 index 33ae912419d..00000000000 --- a/src/NServiceBus.Core/ObjectBuilder/Common/ComponentConfig.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace NServiceBus.ObjectBuilder.Common -{ - using System; - using System.Linq.Expressions; - using Utils.Reflection; - - class ComponentConfig : IComponentConfig - { - private Type component; - private IContainer container; - - public ComponentConfig(Type component, IContainer container) - { - this.component = component; - this.container = container; - } - - IComponentConfig IComponentConfig.ConfigureProperty(string name, object value) - { - container.ConfigureProperty(component, name, value); - - return this; - } - } - - class ComponentConfig : ComponentConfig, IComponentConfig - { - public ComponentConfig(IContainer container) : base(typeof(T), container) - { - } - - IComponentConfig IComponentConfig.ConfigureProperty(Expression> property, object value) - { - var prop = Reflect.GetProperty(property); - - ((IComponentConfig)this).ConfigureProperty(prop.Name, value); - - return this; - } - } - -} diff --git a/src/NServiceBus.Core/ObjectBuilder/Common/ConfigureContainer_Obsolete.cs b/src/NServiceBus.Core/ObjectBuilder/Common/ConfigureContainer_Obsolete.cs deleted file mode 100644 index d51e72f1671..00000000000 --- a/src/NServiceBus.Core/ObjectBuilder/Common/ConfigureContainer_Obsolete.cs +++ /dev/null @@ -1,32 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus.ObjectBuilder.Common.Config -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UseContainer()`, where configuration is an instance of type `BusConfiguration`.")] - public static class ConfigureContainer - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UseContainer()`, where configuration is an instance of type `BusConfiguration`.")] - public static Configure UsingContainer(this Configure configure) where T : class, IContainer, new() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UseContainer(container)`, where configuration is an instance of type `BusConfiguration`.")] - public static Configure UsingContainer(this Configure configure, T container) where T : IContainer - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/Common/IContainer.cs b/src/NServiceBus.Core/ObjectBuilder/Common/IContainer.cs index 5b726b0f2c0..78638650bd8 100644 --- a/src/NServiceBus.Core/ObjectBuilder/Common/IContainer.cs +++ b/src/NServiceBus.Core/ObjectBuilder/Common/IContainer.cs @@ -11,7 +11,7 @@ public interface IContainer : IDisposable /// /// Returns an instantiation of the given type. /// - /// The to build. + /// The to build. /// The component instance. object Build(Type typeToBuild); @@ -27,33 +27,24 @@ public interface IContainer : IDisposable /// with the given type. /// /// Type to be build. - /// Enumeration of all types that implement . + /// Enumeration of all types that implement . IEnumerable BuildAll(Type typeToBuild); /// /// Configures the call model of the given component type. /// - /// Type to be configured - /// The desired lifecycle for this type + /// Type to be configured. + /// The desired lifecycle for this type. void Configure(Type component, DependencyLifecycle dependencyLifecycle); /// - /// Configures the call model of the given component type using a . + /// Configures the call model of the given component type using a . /// /// Type to be configured. - /// to use to configure. + /// to use to configure. /// The desired lifecycle for this type. void Configure(Func component, DependencyLifecycle dependencyLifecycle); - /// - /// Sets the value to be configured for the given property of the - /// given component type. - /// - /// The interface type. - /// The property name to be injected. - /// The value to assign to the . - void ConfigureProperty(Type component, string property, object value); - /// /// Registers the given instance as the singleton that will be returned for the given type. /// @@ -65,7 +56,7 @@ public interface IContainer : IDisposable /// Indicates if a component of the given type has been configured. /// /// Component type to check. - /// true if the is registered in the container or false otherwise. + /// true if the is registered in the container or false otherwise. bool HasComponent(Type componentType); /// @@ -74,4 +65,4 @@ public interface IContainer : IDisposable /// The component instance to release. void Release(object instance); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/IBuilder.cs b/src/NServiceBus.Core/ObjectBuilder/IBuilder.cs index eee3060b614..6f7701e0afa 100644 --- a/src/NServiceBus.Core/ObjectBuilder/IBuilder.cs +++ b/src/NServiceBus.Core/ObjectBuilder/IBuilder.cs @@ -13,7 +13,7 @@ public interface IBuilder : IDisposable /// /// Returns an instantiation of the given type. /// - /// The to build. + /// The to build. /// The component instance. object Build(Type typeToBuild); @@ -28,20 +28,21 @@ public interface IBuilder : IDisposable /// Creates an instance of the given type, injecting it with all defined dependencies. /// /// Type to be resolved. - /// Instance of + /// Instance of . T Build(); /// - /// For each type that is compatible with T, an instance is created with all dependencies injected, and yielded to the caller. + /// For each type that is compatible with T, an instance is created with all dependencies injected, and yielded to the + /// caller. /// /// Type to be resolved. - /// Instances of + /// Instances of . IEnumerable BuildAll(); /// /// For each type that is compatible with the given type, an instance is created with all dependencies injected. /// - /// The to build. + /// The to build. /// The component instances. IEnumerable BuildAll(Type typeToBuild); @@ -55,8 +56,8 @@ public interface IBuilder : IDisposable /// Builds an instance of the defined type injecting it with all defined dependencies /// and invokes the given action on the instance. /// - /// The to build. + /// The to build. /// The callback to call. void BuildAndDispatch(Type typeToBuild, Action action); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ObjectBuilder/IComponentConfig.cs b/src/NServiceBus.Core/ObjectBuilder/IComponentConfig.cs deleted file mode 100644 index d31918a8760..00000000000 --- a/src/NServiceBus.Core/ObjectBuilder/IComponentConfig.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.ObjectBuilder -{ - using System; - using System.Linq.Expressions; - - /// - /// Used to configure the values to be set for the various - /// properties on a component. - /// - public interface IComponentConfig - { - /// - /// Configures the value of the named property of the component. - /// - IComponentConfig ConfigureProperty(string name, object value); - } - - /// - /// Strongly typed version of IComponentConfig - /// - public interface IComponentConfig - { - /// - /// Configures the value of the property like so: - /// ConfigureProperty(o => o.Property, value); - /// - IComponentConfig ConfigureProperty(Expression> property, object value); - } -} diff --git a/src/NServiceBus.Core/ObjectBuilder/IConfigureComponents.cs b/src/NServiceBus.Core/ObjectBuilder/IConfigureComponents.cs index a02812fba93..81e45a19814 100644 --- a/src/NServiceBus.Core/ObjectBuilder/IConfigureComponents.cs +++ b/src/NServiceBus.Core/ObjectBuilder/IConfigureComponents.cs @@ -1,7 +1,6 @@ namespace NServiceBus.ObjectBuilder { using System; - using System.Linq.Expressions; /// /// Used to configure components in the container. @@ -14,48 +13,38 @@ public interface IConfigureComponents /// /// The concrete implementation of the component. /// Defines lifecycle semantics for the given type. - IComponentConfig ConfigureComponent(Type concreteComponent, DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(Type concreteComponent, DependencyLifecycle dependencyLifecycle); /// /// Configures the given type, allowing to fluently configure properties. /// /// Defines lifecycle semantics for the given type. - IComponentConfig ConfigureComponent(DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(DependencyLifecycle dependencyLifecycle); /// /// Configures the given type, allowing to fluently configure properties. /// - /// Type to configure - /// Factory method that returns the given type + /// Type to configure. + /// Factory method that returns the given type. /// Defines lifecycle semantics for the given type. - IComponentConfig ConfigureComponent(Func componentFactory, DependencyLifecycle dependencyLifecycle); + void ConfigureComponent(Func componentFactory, DependencyLifecycle dependencyLifecycle); /// /// Configures the given type, allowing to fluently configure properties. /// - IComponentConfig ConfigureComponent(Func componentFactory, DependencyLifecycle dependencyLifecycle); - - /// - /// Configures the given property of the given type to be injected with the given value. - /// - IConfigureComponents ConfigureProperty(Expression> property, object value); - - /// - /// Configures the given property of the given type to be injected with the given value. - /// - IConfigureComponents ConfigureProperty(string propertyName, object value); + void ConfigureComponent(Func componentFactory, DependencyLifecycle dependencyLifecycle); /// /// Registers the given instance as the singleton that will be returned /// for the given type. /// - IConfigureComponents RegisterSingleton(Type lookupType, object instance); + void RegisterSingleton(Type lookupType, object instance); /// /// Registers the given instance as the singleton that will be returned /// for the given type. /// - IConfigureComponents RegisterSingleton(T instance); + void RegisterSingleton(T instance); /// /// Indicates if a component of the given type has been configured. @@ -67,4 +56,4 @@ public interface IConfigureComponents /// bool HasComponent(Type componentType); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Observable.cs b/src/NServiceBus.Core/Observable.cs deleted file mode 100644 index 4518185e856..00000000000 --- a/src/NServiceBus.Core/Observable.cs +++ /dev/null @@ -1,183 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Threading; - using Janitor; - - [SkipWeaving] - class Observable : IObservable, IDisposable - { - List> observers; - bool isDisposed; - int disposeSignaled; - ReaderWriterLockSlim observerLock = new ReaderWriterLockSlim(); - object disposingLock = new object(); - - public Observable() - { - observers = new List>(); - } - - public void Dispose() - { - if (Interlocked.Exchange(ref disposeSignaled, 1) != 0) - { - return; - } - - lock (disposingLock) - { - observerLock.EnterReadLock(); - try - { - foreach (var observer in observers) - { - observer.OnCompleted(); - } - } - finally - { - observerLock.ExitReadLock(); - } - - observerLock.Dispose(); - observerLock = null; - observers = null; - isDisposed = true; - } - } - - public IDisposable Subscribe(IObserver observer) - { - if (observer == null) - { - throw new ArgumentNullException("observer", "observer is null."); - } - - CheckDisposed(); - - observerLock.EnterWriteLock(); - try - { - observers.Add(observer); - } - finally - { - observerLock.ExitWriteLock(); - } - - return new Unsubscriber(this, observer); - } - - public void OnNext(T value) - { - CheckDisposed(); - - observerLock.EnterReadLock(); - try - { - foreach (var observer in observers) - { - observer.OnNext(value); - } - } - finally - { - observerLock.ExitReadLock(); - } - } - - public void OnError(Exception ex) - { - CheckDisposed(); - - observerLock.EnterReadLock(); - try - { - foreach (var observer in observers) - { - observer.OnError(ex); - } - } - finally - { - observerLock.ExitReadLock(); - } - } - - public void OnCompleted() - { - CheckDisposed(); - - observerLock.EnterReadLock(); - try - { - foreach (var observer in observers) - { - observer.OnCompleted(); - } - } - finally - { - observerLock.ExitReadLock(); - } - } - - void Unsubscribe(IObserver observer) - { - lock (disposingLock) - { - CheckDisposed(); - - observerLock.EnterWriteLock(); - try - { - observers.Remove(observer); - } - finally - { - observerLock.ExitWriteLock(); - } - } - } - - void CheckDisposed() - { - if (isDisposed) - { - throw new ObjectDisposedException("Observable"); - } - } - - [SkipWeaving] - class Unsubscriber : IDisposable - { - Observable observable; - IObserver observer; - - public Unsubscriber(Observable observable, IObserver observer) - { - this.observable = observable; - this.observer = observer; - } - - public void Dispose() - { - var o = Interlocked.Exchange(ref observer, null); - if (o != null) - { - try - { - observable.Unsubscribe(observer); - } - catch (ObjectDisposedException) - { - //Observable has already been disposed, safe to ignore - } - observable = null; - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Obsoletes.cs b/src/NServiceBus.Core/Obsoletes.cs deleted file mode 100644 index dc72cb09c3f..00000000000 --- a/src/NServiceBus.Core/Obsoletes.cs +++ /dev/null @@ -1,671 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -using System; -namespace NServiceBus -{ - [ObsoleteEx( - Message = "Since the case where this exception was thrown should not be handled by consumers of the API it has been removed", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class MessageConventionException : Exception - { - } -} - -namespace NServiceBus.Persistence -{ - [ObsoleteEx( - Message = "Since the case where this exception was thrown should not be handled by consumers of the API it has been removed", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class ConcurrencyException : Exception - { - } -} - -namespace NServiceBus.Unicast.Transport -{ - [ObsoleteEx( - Message = "Since the case where this exception was thrown should not be handled by consumers of the API it has been removed", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class TransportMessageHandlingFailedException : Exception - { - } -} - -namespace NServiceBus.Unicast.Queuing -{ - [ObsoleteEx( - Message = "Since the case where this exception was thrown should not be handled by consumers of the API it has been removed", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class FailedToSendMessageException : Exception - { - } -} - - - -namespace NServiceBus.IdGeneration -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class CombGuid - { - } -} - -namespace NServiceBus.Utils -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class RegistryReader - { - } -} - -namespace NServiceBus.Utils -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class FileVersionRetriever - { - } -} -namespace NServiceBus.Unicast -{ - [ObsoleteEx(Replacement = "ICallback", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Callback - { - } -} -namespace NServiceBus.Unicast -{ - [ObsoleteEx( - Replacement = "IBus", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class IUnicastBus - { - } -} - -namespace NServiceBus.Hosting -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class GenericHost - { - } -} - -namespace NServiceBus.Hosting -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class IHost - { - } -} - - -namespace System.Threading.Tasks.Schedulers -{ - [ObsoleteEx( - Message = "This class was never intended to be exposed as part of the public API.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public sealed class MTATaskScheduler - { - } -} -namespace NServiceBus.Logging.Log4NetBridge -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class ConfigureInternalLog4NetBridge - { - } -} -namespace NServiceBus.Logging.Loggers -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class ConsoleLogger - { - } -} -namespace NServiceBus.Logging.Loggers -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class ConsoleLoggerFactory - { - } -} -namespace NServiceBus.Logging.Loggers.Log4NetAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Log4NetAppenderFactory - { - } -} -namespace NServiceBus.Logging.Loggers.Log4NetAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Log4NetConfigurator - { - } -} -namespace NServiceBus.Logging.Loggers.Log4NetAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Log4NetLogger - { - } -} -namespace NServiceBus.Logging.Loggers.Log4NetAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Log4NetLoggerFactory - { - } -} - -namespace NServiceBus.Logging.Loggers.NLogAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", TreatAsErrorFromVersion = "5")] - public class NLogConfigurator - { - } -} - -namespace NServiceBus.Logging.Loggers.NLogAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class NLogLogger - { - } -} -namespace NServiceBus.Logging.Loggers.NLogAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class NLogLoggerFactory - { - } -} -namespace NServiceBus.Logging.Loggers.NLogAdapter -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class NLogTargetFactory - { - } -} -namespace NServiceBus.Logging.Loggers -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class NullLogger - { - } -} -namespace NServiceBus.Logging.Loggers -{ - [ObsoleteEx( - Message = "Sensible defaults for logging are now built into NServicebus. To customise logging there are external nuget packages available to connect NServiceBus to the various popular logging frameworks.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class NullLoggerFactory - { - } -} -namespace NServiceBus.Logging -{ - [ObsoleteEx( - Message = "Since the case where this exception was thrown should not be handled by consumers of the API it has been removed", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class LoggingLibraryException : Exception - { - } -} -namespace NServiceBus -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "`IHandleProfile` is now passed an instance of `Configure`. `IWantCustomInitialization` is now expected to return a new instance of `Configure`.")] - public interface IWantTheEndpointConfig - { - } -} -namespace NServiceBus.Timeout.Core -{ - [ObsoleteEx( - Message = "Timeout management is an internal concern and cannot be replaced.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public interface IManageTimeouts - { - } -} -namespace NServiceBus.Installation.Environments -{ - [ObsoleteEx( - Message = "IEnvironment is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Windows - { - } -} -namespace NServiceBus.Installation -{ - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public interface IEnvironment - { - } -} -namespace NServiceBus.Installation -{ - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class INeedToInstallSomething - { - } -} -namespace NServiceBus -{ - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class Installer - { - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class Install - { - - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Installer ForInstallationOn(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "`IEnvironment` is no longer required instead use the non generic `INeedToInstallSomething` and use `configuration.EnableInstallers()`, where `configuration` is an instance of type `BusConfiguration` to execute them.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Installer ForInstallationOn(this Configure config, string username) - { - throw new NotImplementedException(); - } - } - -} - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class EndpointConventions - { - [ObsoleteEx( - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure DefineEndpointName(this Configure config, Func definesEndpointName) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.EndpointName(myEndpointName)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure DefineEndpointName(this Configure config, string name) - { - throw new NotImplementedException(); - } - - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureRavenPersistence - { - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure CustomiseRavenPersistence(this Configure config, object callback) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetMessageToDatabaseMappingConvention(convention)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure MessageToDatabaseMappingConvention(this Configure config, Func convention) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package.` Use `configuration.UsePersistence()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistence(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetDefaultDocumentStore(...)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistence(this Configure config, string connectionStringName) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetDefaultDocumentStore(...)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistence(this Configure config, string connectionStringName, string database) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetDefaultDocumentStore(...)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistence(this Configure config, Func getConnectionString) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetDefaultDocumentStore(...)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistence(this Configure config, Func getConnectionString, string database) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().SetDefaultDocumentStore(documentStore)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenPersistenceWithStore(this Configure config, object documentStore) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static void RegisterDefaults() - { - } - - } -} -namespace NServiceBus -{ - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureRavenSagaPersister - { - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().For(Storage.Sagas)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenSagaPersister(this Configure config) - { - throw new NotImplementedException(); - } - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureRavenSubscriptionStorage - { - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().For(Storage.Subscriptions)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure RavenSubscriptionStorage(this Configure config) - { - throw new NotImplementedException(); - } - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureTimeoutManager - { - [ObsoleteEx( - Message = "Use `configuration.DisableFeature()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure DisableTimeoutManager(this Configure config) - { - throw new NotImplementedException(); - } - - - [ObsoleteEx( - Message = "Use `configuration.UsePersistence()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure UseInMemoryTimeoutPersister(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "RavenDB has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. Install the nuget package. Use `configuration.UsePersistence().For(Storage.Timeouts)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure UseRavenTimeoutPersister(this Configure config) - { - throw new NotImplementedException(); - } - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class ConfigureUnicastBus - { - - //TODO: add replacement - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Address GetTimeoutManagerAddress(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "UnicastBus is now the default and hence calling this method is redundant. `Bus.Create(configuration)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure UnicastBus(this Configure config) - { - throw new NotImplementedException(); - } - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - Message = "Inject an instance of `IBus` in the constructor and assign that to a field for use", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class MessageHandlerExtensionMethods - { - [ObsoleteEx( - Message = "Inject an instance of `IBus` in the constructor and assign that to a field for use", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static IBus Bus(this IHandleMessages handler) - { - throw new NotImplementedException(); - } - } -} - -namespace NServiceBus.Transports.Msmq -{ - [ObsoleteEx( - Message = "`MsmqUtilities` was never intended to be exposed as part of the public API. PLease copy the required functionality into your codebase.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class MsmqUtilities - { - } -} - -namespace NServiceBus.Unicast.Config -{ - [ObsoleteEx( - Replacement = "Configure", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class ConfigUnicastBus - { - } -} - -namespace NServiceBus.Features -{ - [ObsoleteEx( - Replacement = "NServiceBus.Features.StorageDrivenPublishing", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public class StorageDrivenPublisher - { - } -} - -namespace NServiceBus -{ - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static class TransportReceiverConfig - { - - [ObsoleteEx( - Message = "Use `configuration.UseTransport(transportDefinitionType).ConnectionString()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure UseTransport(this Configure config, Func definesConnectionString) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseTransport(transportDefinitionType).ConnectionString()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5")] - public static Configure UseTransport(this Configure config, Type transportDefinitionType, Func definesConnectionString) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.Core/Outbox/IOutboxStorage.cs b/src/NServiceBus.Core/Outbox/IOutboxStorage.cs deleted file mode 100644 index fcfd33eda98..00000000000 --- a/src/NServiceBus.Core/Outbox/IOutboxStorage.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus.Outbox -{ - using System.Collections.Generic; - - /// - /// Implemented by the persisters to provide outbox storage capabilities - /// - public interface IOutboxStorage - { - /// - /// Tries to find the given message in the outbox - /// - bool TryGet(string messageId, out OutboxMessage message); - - /// - /// Stores - /// - void Store(string messageId, IEnumerable transportOperations); - - - /// - /// Tells the storage that the message has been dispatched and its now safe to clean up the transport operations - /// - void SetAsDispatched(string messageId); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/Outbox.cs b/src/NServiceBus.Core/Outbox/Outbox.cs deleted file mode 100644 index d6b51d5120a..00000000000 --- a/src/NServiceBus.Core/Outbox/Outbox.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using System.Configuration; - using System.ServiceProcess; - using Logging; - using NServiceBus.Outbox; - using Persistence; - using Pipeline; - using Transports; - using Unicast.Transport; - - /// - /// Configure the Outbox. - /// - public class Outbox : Feature - { - internal Outbox() - { - Defaults(s => s.SetDefault(TimeToKeepDeduplicationEntries, TimeSpan.FromDays(5))); - - Prerequisite(c => c.Settings.Get("Transactions.Enabled"),"Outbox isn't needed since the receive transactions has been turned off"); - - Prerequisite(c => - { - if (!c.Settings.Get().RequireOutboxConsent) - { - return true; - } - - return RequireOutboxConsent(c); - },"This transport requires outbox consent"); - - RegisterStartupTask(); - } - - static bool RequireOutboxConsent(FeatureConfigurationContext context) - { - if (context.Settings.GetOrDefault("DisableOutboxTransportCheck")) - { - return true; - } - var configValue = ConfigurationManager.AppSettings.Get("NServiceBus/Outbox"); - - if (configValue == null) - { - throw new Exception(@"To use the Outbox feature with MSMQ or SQLServer transports you need to enable it in your config file. -To do that add the following: - - - - -The reason you need to do this is because we need to ensure that you have read all the documentation regarding this feature and know the limitations when running it under MSMQ or SQLServer transports."); - } - - bool result; - - if (!Boolean.TryParse(configValue, out result)) - { - throw new Exception("Invalid value in \"NServiceBus/Outbox\" AppSetting. Please ensure it is either \"true\" or \"false\"."); - } - - return result; - } - - internal const string TimeToKeepDeduplicationEntries = "Outbox.TimeToKeepDeduplicationEntries"; - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - if (!PersistenceStartup.HasSupportFor(context.Settings)) - { - throw new Exception("Selected persister doesn't have support for outbox storage. Please select another storage or disable the outbox feature using config.Features(f=>f.Disable())"); - } - - context.Pipeline.Register(); - context.Pipeline.Register(); - context.Pipeline.Replace(WellKnownStep.DispatchMessageToTransport, typeof(OutboxSendBehavior), "Sending behavior with a delay sending until all business transactions are committed to the outbox storage"); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.TransactionSettings, new TransactionSettings(context.Settings)); - - //make the audit use the outbox as well - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall); - context.Container.ConfigureProperty(t => t.AuditerImplType, typeof(OutboxAwareAuditer)); - } - - } - - class DtcRunningWarning :FeatureStartupTask - { - protected override void OnStart() - { - try - { - var sc = new ServiceController - { - ServiceName = "MSDTC", - MachineName = "." - }; - - if (sc.Status == ServiceControllerStatus.Running) - { - log.Warn(@"We have detected that MSDTC service is running on your machine. -Because you have configured this endpoint to run with Outbox enabled we recommend turning MSDTC off to ensure that the Outbox behavior is working as expected and no other resources are enlisting in distributed transactions."); - } - } -// ReSharper disable once EmptyGeneralCatchClause - catch (Exception) - { - // Ignore if we can't check it. - } - - } - - static ILog log = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxAwareAuditer.cs b/src/NServiceBus.Core/Outbox/OutboxAwareAuditer.cs deleted file mode 100644 index 239c07d08a3..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxAwareAuditer.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus.Outbox -{ - using Pipeline; - using Transports; - using Unicast; - - class OutboxAwareAuditer - { - public DefaultMessageAuditer DefaultMessageAuditer { get; set; } - - public PipelineExecutor PipelineExecutor { get; set; } - - public void Audit( SendOptions sendOptions, TransportMessage message) - { - var context = PipelineExecutor.CurrentContext; - - OutboxMessage currentOutboxMessage; - - if (context.TryGet(out currentOutboxMessage)) - { - message.RevertToOriginalBodyIfNeeded(); - - currentOutboxMessage.TransportOperations.Add(new TransportOperation(message.Id, sendOptions.ToTransportOperationOptions(true), message.Body, message.Headers)); - } - else - { - DefaultMessageAuditer.Audit(sendOptions, message); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxConfigExtensions.cs b/src/NServiceBus.Core/Outbox/OutboxConfigExtensions.cs deleted file mode 100644 index 23564a17b2e..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxConfigExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus -{ - using Outbox; - - /// - /// Config methods for the outbox - /// - public static class OutboxConfigExtensions - { - /// - /// Enables the outbox feature - /// - public static OutboxSettings EnableOutbox(this BusConfiguration config) - { - var outboxSettings = new OutboxSettings(config.Settings); - config.Transactions() - .DisableDistributedTransactions() - .DoNotWrapHandlersExecutionInATransactionScope(); - config.EnableFeature(); - return outboxSettings; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxDeduplicationBehavior.cs b/src/NServiceBus.Core/Outbox/OutboxDeduplicationBehavior.cs deleted file mode 100644 index 24c710dca46..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxDeduplicationBehavior.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Transactions; - using NServiceBus.Outbox; - using Pipeline; - using Pipeline.Contexts; - using Transports; - using Unicast; - using Unicast.Messages; - using Unicast.Transport; - - class OutboxDeduplicationBehavior : IBehavior - { - public IOutboxStorage OutboxStorage { get; set; } - public DispatchMessageToTransportBehavior DispatchMessageToTransportBehavior { get; set; } - - public MessageMetadataRegistry MessageMetadataRegistry { get; set; } - - public DefaultMessageAuditer DefaultMessageAuditer { get; set; } - - public TransactionSettings TransactionSettings { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var messageId = context.PhysicalMessage.Id; - OutboxMessage outboxMessage; - - if (!OutboxStorage.TryGet(messageId, out outboxMessage)) - { - outboxMessage = new OutboxMessage(messageId); - - context.Set(outboxMessage); - - //we use this scope to make sure that we escalate to DTC if the user is talking to another resource by misstake - using (var checkForEscalationScope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = TransactionSettings.IsolationLevel, Timeout = TransactionSettings.TransactionTimeout })) - { - next(); - checkForEscalationScope.Complete(); - } - - - if (context.handleCurrentMessageLaterWasCalled) - { - return; - } - } - - DispatchOperationToTransport(outboxMessage.TransportOperations); - - OutboxStorage.SetAsDispatched(messageId); - } - - void DispatchOperationToTransport(IEnumerable operations) - { - foreach (var transportOperation in operations) - { - var deliveryOptions = transportOperation.Options.ToDeliveryOptions(); - - deliveryOptions.EnlistInReceiveTransaction = false; - - var message = new TransportMessage(transportOperation.MessageId, transportOperation.Headers) - { - Body = transportOperation.Body - }; - - string ttbr; - - if (transportOperation.Options.TryGetValue("TimeToBeReceived", out ttbr)) - { - message.TimeToBeReceived = TimeSpan.Parse(ttbr); - } - - //dispatch to transport - - if (transportOperation.Options["Operation"] != "Audit") - { - DispatchMessageToTransportBehavior.InvokeNative(deliveryOptions, message); - } - else - { - DefaultMessageAuditer.Audit(deliveryOptions as SendOptions, message); - } - - } - } - - public class OutboxDeduplicationRegistration : RegisterStep - { - public OutboxDeduplicationRegistration() - : base("OutboxDeduplication", typeof(OutboxDeduplicationBehavior), "Deduplication for the outbox feature") - { - InsertAfter(WellKnownStep.CreateChildContainer); - InsertBefore(WellKnownStep.ExecuteUnitOfWork); - InsertBeforeIfExists(WellKnownStep.AuditProcessedMessage); - } - } - } -} diff --git a/src/NServiceBus.Core/Outbox/OutboxMessage.cs b/src/NServiceBus.Core/Outbox/OutboxMessage.cs deleted file mode 100644 index 5a449afce97..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxMessage.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Outbox -{ - using System; - using System.Collections.Generic; - - /// - /// The Outbox message type. - /// - public class OutboxMessage - { - /// - /// Creates an instance of an . - /// - /// The message identifier of the incoming message. - public OutboxMessage(string messageId) - { - if (messageId == null) - { - throw new ArgumentNullException("messageId"); - } - - MessageId = messageId; - } - - /// - /// Gets the message identifier of the incoming message. - /// - public string MessageId { get; private set; } - - /// - /// The list of operations performed during the processing of the incoming message. - /// - public List TransportOperations - { - get - { - if (transportOperations == null) - { - transportOperations = new List(); - } - - return transportOperations; - } - } - - List transportOperations; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxRecordBehavior.cs b/src/NServiceBus.Core/Outbox/OutboxRecordBehavior.cs deleted file mode 100644 index 73a6bddb32f..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxRecordBehavior.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Outbox; - using Pipeline; - using Pipeline.Contexts; - - class OutboxRecordBehavior : IBehavior - { - public IOutboxStorage OutboxStorage { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - next(); - - if (context.handleCurrentMessageLaterWasCalled) - { - return; - } - - var outboxMessage = context.Get(); - - OutboxStorage.Store(outboxMessage.MessageId, outboxMessage.TransportOperations); - } - - public class OutboxRecorderRegistration : RegisterStep - { - public OutboxRecorderRegistration() - : base("OutboxRecorder", typeof(OutboxRecordBehavior), "Records all action to the outbox storage") - { - InsertBefore(WellKnownStep.MutateIncomingTransportMessage); - InsertAfter(WellKnownStep.ExecuteUnitOfWork); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxSendBehavior.cs b/src/NServiceBus.Core/Outbox/OutboxSendBehavior.cs deleted file mode 100644 index e2fa93aaf60..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxSendBehavior.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Outbox; - using Pipeline; - using Pipeline.Contexts; - - class OutboxSendBehavior : IBehavior - { - public DispatchMessageToTransportBehavior DispatchMessageToTransportBehavior { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - OutboxMessage currentOutboxMessage; - - if (context.TryGet(out currentOutboxMessage)) - { - var options = context.DeliveryOptions.ToTransportOperationOptions(); - var transportOperation = new TransportOperation(context.OutgoingMessage.Id, options, context.OutgoingMessage.Body, context.OutgoingMessage.Headers); - - if (context.OutgoingMessage.TimeToBeReceived != TimeSpan.MaxValue) - { - options["TimeToBeReceived"] = context.OutgoingMessage.TimeToBeReceived.ToString(); - } - - currentOutboxMessage.TransportOperations.Add(transportOperation); - } - else - { - DispatchMessageToTransportBehavior.InvokeNative(context.DeliveryOptions, context.OutgoingMessage); - - next(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/OutboxSettings.cs b/src/NServiceBus.Core/Outbox/OutboxSettings.cs deleted file mode 100644 index 20836e5bd37..00000000000 --- a/src/NServiceBus.Core/Outbox/OutboxSettings.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace NServiceBus.Outbox -{ - using System; - using Settings; - - /// - /// Custom settings related to the outbox feature - /// - public class OutboxSettings - { - readonly SettingsHolder settings; - - internal OutboxSettings(SettingsHolder settings) - { - this.settings = settings; - } - - /// - /// Specifies how long the outbox should keep message data in storage to be able to deduplicate. - /// - /// The new duration to be used - public void TimeToKeepDeduplicationData(TimeSpan time) - { - if (time <= TimeSpan.Zero) - { - throw new Exception("Time must be greater than 0"); - } - - settings.Set(Features.Outbox.TimeToKeepDeduplicationEntries,time); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/TransportOperation.cs b/src/NServiceBus.Core/Outbox/TransportOperation.cs deleted file mode 100644 index 1d679b812e7..00000000000 --- a/src/NServiceBus.Core/Outbox/TransportOperation.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus.Outbox -{ - using System; - using System.Collections.Generic; - - /// - /// Outgoing message operation. - /// - public class TransportOperation - { - /// - /// Creates a new instance of a . - /// - /// The identifier of the outgoing message. - /// The sending options. - /// The message body. - /// The message headers. - /// . - public TransportOperation(string messageId, Dictionary options, byte[] body, Dictionary headers) - { - if (messageId == null) - { - throw new ArgumentNullException("messageId"); - } - - MessageId = messageId; - Options = options; - Body = body; - Headers = headers; - } - - /// - /// Gets the identifier of the outgoing message. - /// - public string MessageId { get; private set; } - - /// - /// Sending options. - /// - public Dictionary Options { get; private set; } - - /// - /// Gets a byte array to the body content of the outgoing message - /// - public byte[] Body { get; private set; } - - /// - /// Gets outgoing message headers. - /// - public Dictionary Headers { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Outbox/TransportOperationConverter.cs b/src/NServiceBus.Core/Outbox/TransportOperationConverter.cs deleted file mode 100644 index c5b021d85b9..00000000000 --- a/src/NServiceBus.Core/Outbox/TransportOperationConverter.cs +++ /dev/null @@ -1,137 +0,0 @@ -namespace NServiceBus.Outbox -{ - using System; - using System.Collections.Generic; - using Unicast; - - static class TransportOperationConverter - { - public static Dictionary ToTransportOperationOptions(this DeliveryOptions options, bool isAudit = false) - { - var result = new Dictionary(); - - var sendOptions = options as SendOptions; - - string operation; - - if (sendOptions != null) - { - operation = sendOptions is ReplyOptions ? "Reply" : "Send"; - - if (isAudit) - { - operation = "Audit"; - } - - if (sendOptions.DelayDeliveryWith.HasValue) - { - result["DelayDeliveryWith"] = sendOptions.DelayDeliveryWith.Value.ToString(); - } - - if (sendOptions.DeliverAt.HasValue) - { - result["DeliverAt"] = DateTimeExtensions.ToWireFormattedString(sendOptions.DeliverAt.Value); - } - - - if (sendOptions.TimeToBeReceived.HasValue) - { - result["TimeToBeReceived"] = sendOptions.TimeToBeReceived.ToString(); - } - - result["CorrelationId"] = sendOptions.CorrelationId; - result["Destination"] = sendOptions.Destination.ToString(); - } - else - { - var publishOptions = options as PublishOptions; - - if (publishOptions == null) - { - throw new Exception("Unknown delivery option: " + options.GetType().FullName); - } - - operation = "Publish"; - result["EventType"] = publishOptions.EventType.AssemblyQualifiedName; - } - - if (options.ReplyToAddress != null) - { - result["ReplyToAddress"] = options.ReplyToAddress.ToString(); - } - - result["Operation"] = operation; - - - return result; - } - - public static DeliveryOptions ToDeliveryOptions(this Dictionary options) - { - var operation = options["Operation"].ToLower(); - - switch (operation) - { - case "publish": - return new PublishOptions(Type.GetType(options["EventType"])) - { - ReplyToAddress = Address.Parse(options["ReplyToAddress"]) - }; - - case "send": - case "audit": - var sendOptions = new SendOptions(options["Destination"]); - - ApplySendOptionSettings(sendOptions, options); - - return sendOptions; - - case "reply": - var replyOptions = new ReplyOptions(Address.Parse(options["Destination"]), options["CorrelationId"]) - { - ReplyToAddress = Address.Parse(options["ReplyToAddress"]) - }; - ApplySendOptionSettings(replyOptions, options); - - return replyOptions; - - default: - throw new Exception("Unknown operation: " + operation); - } - - - } - static void ApplySendOptionSettings(SendOptions sendOptions, Dictionary options) - { - string delayDeliveryWith; - if (options.TryGetValue("DelayDeliveryWith", out delayDeliveryWith)) - { - sendOptions.DelayDeliveryWith = TimeSpan.Parse(delayDeliveryWith); - } - - string deliverAt; - if (options.TryGetValue("DeliverAt", out deliverAt)) - { - sendOptions.DeliverAt = DateTimeExtensions.ToUtcDateTime(deliverAt); - } - - string timeToBeReceived; - if (options.TryGetValue("TimeToBeReceived", out timeToBeReceived)) - { - sendOptions.TimeToBeReceived = TimeSpan.Parse(timeToBeReceived); - } - - sendOptions.CorrelationId = options["CorrelationId"]; - - string replyToAddress; - if (options.TryGetValue("ReplyToAddress", out replyToAddress)) - { - sendOptions.ReplyToAddress = Address.Parse(replyToAddress); - } - - - sendOptions.CorrelationId = options["CorrelationId"]; - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/AuditProcessingStatisticsBehavior.cs b/src/NServiceBus.Core/Performance/AuditProcessingStatisticsBehavior.cs new file mode 100644 index 00000000000..df97c0a3676 --- /dev/null +++ b/src/NServiceBus.Core/Performance/AuditProcessingStatisticsBehavior.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class AuditProcessingStatisticsBehavior : IBehavior + { + public Task Invoke(IAuditContext context, Func next) + { + ProcessingStatisticsBehavior.State state; + + if (context.Extensions.TryGet(out state)) + { + context.AddAuditData(Headers.ProcessingStarted, DateTimeExtensions.ToWireFormattedString(state.ProcessingStarted)); + // We can't take the processing time from the state since we don't know it yet. + context.AddAuditData(Headers.ProcessingEnded, DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow)); + } + + return next(context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Counters/IPerformanceCounterInstance.cs b/src/NServiceBus.Core/Performance/Counters/IPerformanceCounterInstance.cs new file mode 100644 index 00000000000..c0bd7bf826b --- /dev/null +++ b/src/NServiceBus.Core/Performance/Counters/IPerformanceCounterInstance.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + using System; + + interface IPerformanceCounterInstance : IDisposable + { + void Increment(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Counters/NonFunctionalPerformanceCounterInstance.cs b/src/NServiceBus.Core/Performance/Counters/NonFunctionalPerformanceCounterInstance.cs new file mode 100644 index 00000000000..328327b3ed2 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Counters/NonFunctionalPerformanceCounterInstance.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + class NonFunctionalPerformanceCounterInstance : IPerformanceCounterInstance + { + public void Increment() + { + //NOOP + } + + public void Dispose() + { + //NOOP + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Counters/PerformanceCounterHelper.cs b/src/NServiceBus.Core/Performance/Counters/PerformanceCounterHelper.cs new file mode 100644 index 00000000000..e2329c96188 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Counters/PerformanceCounterHelper.cs @@ -0,0 +1,60 @@ +namespace NServiceBus +{ + using System; + using System.Diagnostics; + using Logging; + + static class PerformanceCounterHelper + { + public static PerformanceCounter InstantiatePerformanceCounter(string counterName, string instanceName) + { + PerformanceCounter counter; + + TryToInstantiatePerformanceCounter(counterName, instanceName, out counter, true); + + return counter; + } + + public static IPerformanceCounterInstance TryToInstantiatePerformanceCounter(string counterName, string instanceName) + { + PerformanceCounter counter; + var success = TryToInstantiatePerformanceCounter(counterName, instanceName, out counter, false); + if (success) + { + return new PerformanceCounterInstance(counter); + } + return new NonFunctionalPerformanceCounterInstance(); + } + + static bool TryToInstantiatePerformanceCounter(string counterName, string instanceName, out PerformanceCounter counter, bool throwIfFails) + { + if (instanceName.Length > 128) + { + throw new Exception($"The endpoint name ('{instanceName}') is too long (longer then {(int) sbyte.MaxValue}) to register as a performance counter instance name. Reduce the endpoint name."); + } + + var message = $"NServiceBus performance counter for '{counterName}' is not set up correctly. To rectify this problem download the latest powershell commandlets from http://www.particular.net downloads page."; + + try + { + counter = new PerformanceCounter("NServiceBus", counterName, instanceName, false); + } + catch (Exception ex) + { + if (throwIfFails) + { + throw new InvalidOperationException(message, ex); + } + + logger.Info(message); + counter = null; + + return false; + } + logger.DebugFormat("'{0}' counter initialized for '{1}'", counterName, instanceName); + return true; + } + + static ILog logger = LogManager.GetLogger(typeof(PerformanceCounterHelper)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Counters/PerformanceCounterInstance.cs b/src/NServiceBus.Core/Performance/Counters/PerformanceCounterInstance.cs new file mode 100644 index 00000000000..898e0078d54 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Counters/PerformanceCounterInstance.cs @@ -0,0 +1,24 @@ +namespace NServiceBus +{ + using System.Diagnostics; + + class PerformanceCounterInstance : IPerformanceCounterInstance + { + public PerformanceCounterInstance(PerformanceCounter counter) + { + this.counter = counter; + } + + public void Increment() + { + counter.Increment(); + } + + public void Dispose() + { + //Injected via Fody + } + + PerformanceCounter counter; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Counters/PerformanceMonitorUsersInstaller.cs b/src/NServiceBus.Core/Performance/Counters/PerformanceMonitorUsersInstaller.cs new file mode 100644 index 00000000000..9a1b2e04cf2 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Counters/PerformanceMonitorUsersInstaller.cs @@ -0,0 +1,106 @@ +namespace NServiceBus +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Security.Principal; + using System.Threading.Tasks; + using Installation; + using Logging; + + // Add the identity to the 'Performance Monitor Users' local group + class PerformanceMonitorUsersInstaller : INeedToInstallSomething + { + static PerformanceMonitorUsersInstaller() + { + builtinPerformanceMonitoringUsersName = new SecurityIdentifier(WellKnownSidType.BuiltinPerformanceMonitoringUsersSid, null).Translate(typeof(NTAccount)).ToString(); + var parts = builtinPerformanceMonitoringUsersName.Split('\\'); + + if (parts.Length == 2) + { + builtinPerformanceMonitoringUsersName = parts[1]; + } + } + + public Task Install(string identity) + { + //did not use DirectoryEntry to avoid a ref to the DirectoryServices.dll + try + { + if (!ElevateChecker.IsCurrentUserElevated()) + { + logger.InfoFormat(@"Did not attempt to add user '{0}' to group '{1}' since the process is not running with elevated privileges. Processing will continue. To manually perform this action, run the following command from an admin console: +net localgroup ""{1}"" ""{0}"" /add", identity, builtinPerformanceMonitoringUsersName); + return TaskEx.CompletedTask; + } + StartProcess(identity); + } + catch (Exception win32Exception) + { + var message = string.Format( + @"Failed adding user '{0}' to group '{1}' due to an Exception. +To help diagnose the problem try running the following command from an admin console: +net localgroup ""{1}"" ""{0}"" /add", identity, builtinPerformanceMonitoringUsersName); + logger.Warn(message, win32Exception); + } + + return TaskEx.CompletedTask; + } + + + void StartProcess(string identity) + { + //net localgroup "Performance Monitor Users" "{user account}" /add + var startInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + Arguments = string.Format("localgroup \"{1}\" \"{0}\" /add", identity, builtinPerformanceMonitoringUsersName), + FileName = "net", + WorkingDirectory = Path.GetTempPath() + }; + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(5000); + + if (process.ExitCode == 0) + { + logger.Info($"Added user '{identity}' to group '{builtinPerformanceMonitoringUsersName}'."); + return; + } + var error = process.StandardError.ReadToEnd(); + if (IsAlreadyAMemberError(error)) + { + logger.Info($"Skipped adding user '{identity}' to group '{builtinPerformanceMonitoringUsersName}' because the user is already in group."); + return; + } + if (IsGroupDoesNotExistError(error)) + { + logger.Info($"Skipped adding user '{identity}' to group '{builtinPerformanceMonitoringUsersName}' because the group does not exist."); + return; + } + var message = string.Format( + @"Failed to add user '{0}' to group '{2}'. +Error: {1} +To help diagnose the problem try running the following command from an admin console: +net localgroup ""{2}"" ""{0}"" /add", identity, error, builtinPerformanceMonitoringUsersName); + logger.Info(message); + } + } + + static bool IsAlreadyAMemberError(string error) + { + return error.Contains("1378"); + } + + static bool IsGroupDoesNotExistError(string error) + { + //required since 'Performance Monitor Users' does not exist on all windows OS. + return error.Contains("1376"); + } + + static ILog logger = LogManager.GetLogger(); + static string builtinPerformanceMonitoringUsersName; + } +} diff --git a/src/NServiceBus.Core/Performance/MessageDurability/DetermineMessageDurabilityBehavior.cs b/src/NServiceBus.Core/Performance/MessageDurability/DetermineMessageDurabilityBehavior.cs new file mode 100644 index 00000000000..99bca8751b5 --- /dev/null +++ b/src/NServiceBus.Core/Performance/MessageDurability/DetermineMessageDurabilityBehavior.cs @@ -0,0 +1,33 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Concurrent; + using System.Threading.Tasks; + using DeliveryConstraints; + using Pipeline; + + class DetermineMessageDurabilityBehavior : IBehavior + { + public DetermineMessageDurabilityBehavior(Func convention) + { + this.convention = convention; + durabilityCache = new ConcurrentDictionary(); + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + if (durabilityCache.GetOrAdd(context.Message.MessageType, t => convention(t))) + { + context.Extensions.AddDeliveryConstraint(new NonDurableDelivery()); + + context.Headers[Headers.NonDurableMessage] = true.ToString(); + } + + return next(context); + } + + Func convention; + + ConcurrentDictionary durabilityCache; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConfig.cs b/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConfig.cs new file mode 100644 index 00000000000..361b6d6bdb3 --- /dev/null +++ b/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConfig.cs @@ -0,0 +1,57 @@ +namespace NServiceBus +{ + using System; + using Settings; + + /// + /// Configuration class for durable messaging. + /// + public static class DurableMessagesConfig + { + /// + /// Configures messages to be guaranteed to be delivered in the event of a computer failure or network problem. + /// + /// The instance to apply the settings to. + public static void EnableDurableMessages(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + config.Settings.Set("Endpoint.DurableMessages", true); + } + + /// + /// Configures messages that are not guaranteed to be delivered in the event of a computer failure or network problem. + /// + /// The instance to apply the settings to. + public static void DisableDurableMessages(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + config.Settings.Set("Endpoint.DurableMessages", false); + } + + /// + /// Returns whether durable messages are on or off. + /// + public static bool DurableMessagesEnabled(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + bool durableMessagesEnabled; + if (settings.TryGet("Endpoint.DurableMessages", out durableMessagesEnabled)) + { + return durableMessagesEnabled; + } + return true; + } + + /// + /// Returns whether durable messages are on or off. + /// + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "DurableMessagesEnabled")] + public static bool DurableMessagesEnabled(this Configure config) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConventionExtensions.cs b/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConventionExtensions.cs new file mode 100644 index 00000000000..5ca38479e51 --- /dev/null +++ b/src/NServiceBus.Core/Performance/MessageDurability/DurableMessagesConventionExtensions.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System; + + /// + /// Add extensions to allow conventions for message durability to be changed. + /// + public static class DurableMessagesConventionExtensions + { + /// + /// Sets the function to be used to evaluate whether a type is an express message or not. + /// + public static ConventionsBuilder DefiningExpressMessagesAs(this ConventionsBuilder builder, Func definesExpressMessageType) + { + Guard.AgainstNull(nameof(builder), builder); + Guard.AgainstNull(nameof(definesExpressMessageType), definesExpressMessageType); + + builder.Settings.Set("messageDurabilityConvention", definesExpressMessageType); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ExpressAttribute.cs b/src/NServiceBus.Core/Performance/MessageDurability/ExpressAttribute.cs similarity index 100% rename from src/NServiceBus.Core/ExpressAttribute.cs rename to src/NServiceBus.Core/Performance/MessageDurability/ExpressAttribute.cs diff --git a/src/NServiceBus.Core/Performance/MessageDurability/MessageDurabilityFeature.cs b/src/NServiceBus.Core/Performance/MessageDurability/MessageDurabilityFeature.cs new file mode 100644 index 00000000000..66ee48bcd6b --- /dev/null +++ b/src/NServiceBus.Core/Performance/MessageDurability/MessageDurabilityFeature.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.Features +{ + using System; + using DeliveryConstraints; + + class MessageDurabilityFeature : Feature + { + public MessageDurabilityFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + defaultToDurableMessages = context.Settings.DurableMessagesEnabled(); + + if (!context.Settings.TryGet("messageDurabilityConvention", out durabilityConvention)) + { + durabilityConvention = t => t.GetCustomAttributes(typeof(ExpressAttribute), true).Length > 0; + } + + doesSupportNonDurableDelivery = context.Settings.DoesTransportSupportConstraint(); + + if (!defaultToDurableMessages && !doesSupportNonDurableDelivery) + { + throw new Exception(DoesNotSupportNonDurableDeliveryExceptionMessage); + } + + context.Pipeline.Register(new DetermineMessageDurabilityBehavior(t => Convention(t, doesSupportNonDurableDelivery, defaultToDurableMessages, durabilityConvention)), "Adds the NonDurableDelivery constraint for messages that have requested to be delivered in non-durable mode"); + } + + public static bool Convention(Type messageType, bool doesSupportNonDurableDelivery, bool defaultToDurableMessages, Func durabilityConvention) + { + var nonDurable = !defaultToDurableMessages || durabilityConvention(messageType); + if (nonDurable && !doesSupportNonDurableDelivery) + { + throw new Exception(DoesNotSupportNonDurableDeliveryExceptionMessage); + } + return nonDurable; + } + + // fields here opt-in for delegate caching. + bool defaultToDurableMessages; + bool doesSupportNonDurableDelivery; + Func durabilityConvention; + const string DoesNotSupportNonDurableDeliveryExceptionMessage = "The configured transport does not support non-durable messages but some messages have been configured to be non-durable (e.g. by using the [Express] attribute). Make the messages durable, or use a transport supporting non-durable messages."; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/MessageProcessingOptimizations/MessageProcessingOptimizationExtensions.cs b/src/NServiceBus.Core/Performance/MessageProcessingOptimizations/MessageProcessingOptimizationExtensions.cs new file mode 100644 index 00000000000..c94a3ff0890 --- /dev/null +++ b/src/NServiceBus.Core/Performance/MessageProcessingOptimizations/MessageProcessingOptimizationExtensions.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + /// + /// Configuration class for durable messaging. + /// + public static class MessageProcessingOptimizationExtensions + { + /// + /// Instructs the transport to limits the allowed concurrency when processing messages. + /// + /// The instance to apply the settings to. + /// The max concurrency allowed. + public static void LimitMessageProcessingConcurrencyTo(this EndpointConfiguration config, int maxConcurrency) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNegativeAndZero(nameof(maxConcurrency), maxConcurrency); + + config.Settings.Set(new ConcurrencyLimit + { + MaxValue = maxConcurrency + }); + } + + internal class ConcurrencyLimit + { + public int MaxValue { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/ReceiveStatisticsFeature.cs b/src/NServiceBus.Core/Performance/ReceiveStatisticsFeature.cs new file mode 100644 index 00000000000..da796cdc0cb --- /dev/null +++ b/src/NServiceBus.Core/Performance/ReceiveStatisticsFeature.cs @@ -0,0 +1,47 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Features; + + class ReceiveStatisticsFeature : Feature + { + public ReceiveStatisticsFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var logicalAddress = context.Settings.LogicalAddress(); + var performanceDiagnosticsBehavior = new ReceivePerformanceDiagnosticsBehavior(logicalAddress.EndpointInstance.Endpoint); + + context.Pipeline.Register(performanceDiagnosticsBehavior, "Provides various performance counters for receive statistics"); + context.Pipeline.Register("ProcessingStatistics", new ProcessingStatisticsBehavior(), "Collects timing for ProcessingStarted and adds the state to determine ProcessingEnded"); + context.Pipeline.Register("AuditProcessingStatistics", new AuditProcessingStatisticsBehavior(), "Add ProcessingStarted and ProcessingEnded headers"); + + context.RegisterStartupTask(new WarmupCooldownTask(performanceDiagnosticsBehavior)); + } + + class WarmupCooldownTask : FeatureStartupTask + { + public WarmupCooldownTask(ReceivePerformanceDiagnosticsBehavior behavior) + { + this.behavior = behavior; + } + + protected override Task OnStart(IMessageSession session) + { + behavior.Warmup(); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + behavior.Cooldown(); + return TaskEx.CompletedTask; + } + + readonly ReceivePerformanceDiagnosticsBehavior behavior; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoring.cs b/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoring.cs new file mode 100644 index 00000000000..eb655569542 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoring.cs @@ -0,0 +1,99 @@ +namespace NServiceBus.Features +{ + using System; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Used to configure CriticalTimeMonitoring. + /// + public class CriticalTimeMonitoring : Feature + { + internal CriticalTimeMonitoring() + { + } + + /// + /// . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + var counterInstanceName = context.Settings.EndpointName(); + var criticalTimeCounter = new CriticalTimeCounter(counterInstanceName); + + var notifications = context.Settings.Get(); + + notifications.Subscribe(e => + { + string timeSentString; + + if (!e.ProcessedMessage.Headers.TryGetValue(Headers.TimeSent, out timeSentString)) + { + return TaskEx.CompletedTask; + } + + criticalTimeCounter.Update(DateTimeExtensions.ToUtcDateTime(timeSentString), e.StartedAt, e.CompletedAt); + + return TaskEx.CompletedTask; + }); + + context.RegisterStartupTask(() => criticalTimeCounter); + } + + class CriticalTimeCounter : FeatureStartupTask + { + public CriticalTimeCounter(string counterInstanceName) + { + this.counterInstanceName = counterInstanceName; + } + + public void Update(DateTime sentInstant, DateTime processingStartedInstant, DateTime processingEndedInstant) + { + var endToEndTime = processingEndedInstant - sentInstant; + counter.RawValue = Convert.ToInt32(endToEndTime.TotalSeconds); + + lastMessageProcessedTime = processingEndedInstant; + + var processingDuration = processingEndedInstant - processingStartedInstant; + estimatedMaximumProcessingDuration = processingDuration.Add(TimeSpan.FromSeconds(1)); + } + + protected override Task OnStart(IMessageSession session) + { + counter = PerformanceCounterHelper.InstantiatePerformanceCounter("Critical Time", counterInstanceName); + timer = new Timer(ResetCounterValueIfNoMessageHasBeenProcessedRecently, null, 0, 2000); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + timer.Dispose(); + counter.Dispose(); + + return TaskEx.CompletedTask; + } + + void ResetCounterValueIfNoMessageHasBeenProcessedRecently(object state) + { + if (NoMessageHasBeenProcessedRecently()) + { + counter.RawValue = 0; + } + } + + bool NoMessageHasBeenProcessedRecently() + { + var timeFromLastMessageProcessed = DateTime.UtcNow - lastMessageProcessedTime; + return timeFromLastMessageProcessed > estimatedMaximumProcessingDuration; + } + + string counterInstanceName; + PerformanceCounter counter; + TimeSpan estimatedMaximumProcessingDuration = TimeSpan.FromSeconds(2); + DateTime lastMessageProcessedTime; + // ReSharper disable once NotAccessedField.Local + Timer timer; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoringConfig.cs b/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoringConfig.cs new file mode 100644 index 00000000000..0ae9274fa98 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/CriticalTime/CriticalTimeMonitoringConfig.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using Features; + + /// + /// Provide configuration options for monitoring related settings. + /// + public static class CriticalTimeMonitoringConfig + { + /// + /// Enables the NServiceBus specific performance counters. + /// + /// The instance to apply the settings to. + public static void EnableCriticalTimePerformanceCounter(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + config.EnableFeature(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs b/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs new file mode 100644 index 00000000000..faea8c7b7bf --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs @@ -0,0 +1,40 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class ProcessingStatisticsBehavior : IBehavior + { + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + var state = new State(); + + string timeSentString; + var headers = context.Message.Headers; + + if (headers.TryGetValue(Headers.TimeSent, out timeSentString)) + { + state.TimeSent = DateTimeExtensions.ToUtcDateTime(timeSentString); + } + + state.ProcessingStarted = DateTime.UtcNow; + context.Extensions.Set(state); + try + { + await next(context).ConfigureAwait(false); + } + finally + { + state.ProcessingEnded = DateTime.UtcNow; + } + } + + public class State + { + public DateTime? TimeSent { get; set; } + public DateTime ProcessingStarted { get; set; } + public DateTime ProcessingEnded { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/ReceivePerformanceDiagnosticsBehavior.cs b/src/NServiceBus.Core/Performance/Statistics/ReceivePerformanceDiagnosticsBehavior.cs new file mode 100644 index 00000000000..2581283cf06 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/ReceivePerformanceDiagnosticsBehavior.cs @@ -0,0 +1,59 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Janitor; + using Pipeline; + + [SkipWeaving] + class ReceivePerformanceDiagnosticsBehavior : IBehavior + { + public ReceivePerformanceDiagnosticsBehavior(string queueName) + { + this.queueName = queueName; + } + + public void Warmup() + { + messagesPulledFromQueueCounter = PerformanceCounterHelper.TryToInstantiatePerformanceCounter( + "# of msgs pulled from the input queue /sec", + queueName); + successRateCounter = PerformanceCounterHelper.TryToInstantiatePerformanceCounter( + "# of msgs successfully processed / sec", + queueName); + failureRateCounter = PerformanceCounterHelper.TryToInstantiatePerformanceCounter( + "# of msgs failures / sec", + queueName); + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + messagesPulledFromQueueCounter.Increment(); + + try + { + await next(context).ConfigureAwait(false); + } + catch (Exception) + { + failureRateCounter.Increment(); + throw; + } + + successRateCounter.Increment(); + } + + public void Cooldown() + { + messagesPulledFromQueueCounter?.Dispose(); + successRateCounter?.Dispose(); + failureRateCounter?.Dispose(); + } + + IPerformanceCounterInstance failureRateCounter; + IPerformanceCounterInstance messagesPulledFromQueueCounter; + IPerformanceCounterInstance successRateCounter; + + string queueName; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoring.cs b/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoring.cs new file mode 100644 index 00000000000..9bad002d776 --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoring.cs @@ -0,0 +1,205 @@ +namespace NServiceBus.Features +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Used to configure SLAMonitoring. + /// + public class SLAMonitoring : Feature + { + internal SLAMonitoring() + { + } + + /// + /// . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + if (context.Settings.GetOrDefault("Endpoint.SendOnly")) + { + throw new Exception("SLA Monitoring is not supported for send only endpoints, please remove .EnableSLAPerformanceCounter(mySLA) from your config."); + } + + TimeSpan endpointSla; + + if (!context.Settings.TryGet(EndpointSLAKey, out endpointSla)) + { + throw new Exception("Endpoint SLA is required for the `SLA violation countdown` counter. Pass the SLA for this endpoint to .EnableSLAPerformanceCounter(mySLA)."); + } + + var counterInstanceName = context.Settings.EndpointName(); + var slaBreachCounter = new EstimatedTimeToSLABreachCounter(endpointSla, counterInstanceName); + + var notifications = context.Settings.Get(); + + notifications.Subscribe(e => + { + string timeSentString; + + if (!e.ProcessedMessage.Headers.TryGetValue(Headers.TimeSent, out timeSentString)) + { + return TaskEx.CompletedTask; + } + + slaBreachCounter.Update(DateTimeExtensions.ToUtcDateTime(timeSentString), e.StartedAt, e.CompletedAt); + + return TaskEx.CompletedTask; + }); + + context.RegisterStartupTask(() => slaBreachCounter); + } + + internal const string EndpointSLAKey = "EndpointSLA"; + + class EstimatedTimeToSLABreachCounter : FeatureStartupTask + { + public EstimatedTimeToSLABreachCounter(TimeSpan endpointSla, string counterInstanceName) + { + this.endpointSla = endpointSla; + this.counterInstanceName = counterInstanceName; + } + + + public void Update(DateTime sent, DateTime processingStarted, DateTime processingEnded) + { + var dataPoint = new DataPoint(processingEnded - sent, processingEnded, processingEnded - processingStarted); + + lock (dataPoints) + { + dataPoints.Add(dataPoint); + if (dataPoints.Count > MaxDataPoints) + { + dataPoints.RemoveRange(0, dataPoints.Count - MaxDataPoints); + } + } + + UpdateTimeToSLABreach(); + } + + protected override Task OnStart(IMessageSession session) + { + counter = PerformanceCounterHelper.InstantiatePerformanceCounter("SLA violation countdown", counterInstanceName); + timer = new Timer(RemoveOldDataPoints, null, 0, 2000); + + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + timer.Dispose(); + counter.Dispose(); + + return TaskEx.CompletedTask; + } + + void UpdateTimeToSLABreach() + { + List snapshots; + + lock (dataPoints) + { + snapshots = new List(dataPoints); + } + + var secondsToSLABreach = CalculateTimeToSLABreach(snapshots); + + counter.RawValue = Convert.ToInt32(Math.Min(secondsToSLABreach, int.MaxValue)); + } + + double CalculateTimeToSLABreach(List snapshots) + { + DataPoint? first = null, previous = null; + + var criticalTimeDelta = TimeSpan.Zero; + + foreach (var current in snapshots) + { + if (!first.HasValue) + { + first = current; + } + + if (previous.HasValue) + { + criticalTimeDelta += current.CriticalTime - previous.Value.CriticalTime; + } + + previous = current; + } + + if (criticalTimeDelta.TotalSeconds <= 0.0) + { + return double.MaxValue; + } + + var elapsedTime = previous.Value.OccurredAt - first.Value.OccurredAt; + + if (elapsedTime.TotalSeconds <= 0.0) + { + return double.MaxValue; + } + + var lastKnownCriticalTime = previous.Value.CriticalTime.TotalSeconds; + + var criticalTimeDeltaPerSecond = criticalTimeDelta.TotalSeconds / elapsedTime.TotalSeconds; + + var secondsToSLABreach = (endpointSla.TotalSeconds - lastKnownCriticalTime) / criticalTimeDeltaPerSecond; + + if (secondsToSLABreach < 0.0) + { + return 0.0; + } + + return secondsToSLABreach; + } + + void RemoveOldDataPoints(object state) + { + lock (dataPoints) + { + var last = dataPoints.Count == 0 ? default(DataPoint?) : dataPoints[dataPoints.Count - 1]; + + if (last.HasValue) + { + var oldestDataToKeep = DateTime.UtcNow - new TimeSpan(last.Value.ProcessingTime.Ticks * 3); + + dataPoints.RemoveAll(d => d.OccurredAt < oldestDataToKeep); + } + } + + UpdateTimeToSLABreach(); + } + + PerformanceCounter counter; + List dataPoints = new List(); + TimeSpan endpointSla; + string counterInstanceName; + // ReSharper disable once NotAccessedField.Local + Timer timer; + + const int MaxDataPoints = 10; + + struct DataPoint + { + public DataPoint(TimeSpan criticalTime, DateTime occurredAt, TimeSpan processingTime) + { + CriticalTime = criticalTime; + OccurredAt = occurredAt; + ProcessingTime = processingTime; + } + + public TimeSpan CriticalTime { get; } + + public DateTime OccurredAt { get; } + + public TimeSpan ProcessingTime { get; } + } + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoringConfig.cs b/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoringConfig.cs new file mode 100644 index 00000000000..738d678588b --- /dev/null +++ b/src/NServiceBus.Core/Performance/Statistics/SLA/SLAMonitoringConfig.cs @@ -0,0 +1,34 @@ +namespace NServiceBus +{ + using System; + using Features; + + /// + /// Provide configuration options for monitoring related settings. + /// + public static class SLAMonitoringConfig + { + /// + /// Enables the NServiceBus specific performance counters with a specific EndpointSLA. + /// + /// The instance to apply the settings to. + /// The to use oa the SLA. Must be greater than . + public static void EnableSLAPerformanceCounter(this EndpointConfiguration config, TimeSpan sla) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNegativeAndZero(nameof(sla), sla); + config.Settings.Set(SLAMonitoring.EndpointSLAKey, sla); + EnableSLAPerformanceCounter(config); + } + + /// + /// Enables the NServiceBus specific performance counters with a specific EndpointSLA. + /// + /// The instance to apply the settings to. + public static void EnableSLAPerformanceCounter(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + config.EnableFeature(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/ApplyTimeToBeReceivedBehavior.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/ApplyTimeToBeReceivedBehavior.cs new file mode 100644 index 00000000000..c135dfc2a60 --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/ApplyTimeToBeReceivedBehavior.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using DeliveryConstraints; + using Performance.TimeToBeReceived; + using Pipeline; + + class ApplyTimeToBeReceivedBehavior : IBehavior + { + public ApplyTimeToBeReceivedBehavior(TimeToBeReceivedMappings timeToBeReceivedMappings) + { + this.timeToBeReceivedMappings = timeToBeReceivedMappings; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + TimeSpan timeToBeReceived; + + if (timeToBeReceivedMappings.TryGetTimeToBeReceived(context.Message.MessageType, out timeToBeReceived)) + { + context.Extensions.AddDeliveryConstraint(new DiscardIfNotReceivedBefore(timeToBeReceived)); + context.Headers[Headers.TimeToBeReceived] = timeToBeReceived.ToString(); + } + + return next(context); + } + + TimeToBeReceivedMappings timeToBeReceivedMappings; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/DiscardIfNotReceivedBefore.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/DiscardIfNotReceivedBefore.cs new file mode 100644 index 00000000000..5029993c088 --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/DiscardIfNotReceivedBefore.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Performance.TimeToBeReceived +{ + using System; + using DeliveryConstraints; + + /// + /// Instructs the transport to discard the message if it hasn't been received. + /// within the specified timespan. + /// + public class DiscardIfNotReceivedBefore : DeliveryConstraint + { + /// + /// Initializes the constraint with a max time. + /// + public DiscardIfNotReceivedBefore(TimeSpan maxTime) + { + MaxTime = maxTime; + } + + /// + /// The max time to wait before discarding the message. + /// + public TimeSpan MaxTime { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceived.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceived.cs new file mode 100644 index 00000000000..5f8dc87532d --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceived.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.Features +{ + using System.Linq; + using DeliveryConstraints; + using Performance.TimeToBeReceived; + using Unicast.Messages; + + class TimeToBeReceived : Feature + { + public TimeToBeReceived() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var mappings = GetMappings(context); + + context.Pipeline.Register("ApplyTimeToBeReceived", new ApplyTimeToBeReceivedBehavior(mappings), "Adds the `DiscardIfNotReceivedBefore` constraint to relevant messages"); + } + + static TimeToBeReceivedMappings GetMappings(FeatureConfigurationContext context) + { + var registry = context.Settings.Get(); + var knownMessages = registry.GetAllMessages().Select(m => m.MessageType); + + var convention = TimeToBeReceivedMappings.DefaultConvention; + + UserDefinedTimeToBeReceivedConvention userDefinedConvention; + if (context.Settings.TryGet(out userDefinedConvention)) + { + convention = userDefinedConvention.GetTimeToBeReceivedForMessage; + } + + var doesTransportSupportDiscardIfNotReceivedBefore = context.Settings.DoesTransportSupportConstraint(); + return new TimeToBeReceivedMappings(knownMessages, convention, doesTransportSupportDiscardIfNotReceivedBefore); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedAttribute.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedAttribute.cs new file mode 100644 index 00000000000..fc0d01024c7 --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedAttribute.cs @@ -0,0 +1,37 @@ +namespace NServiceBus +{ + using System; + + /// + /// Attribute to indicate that a message has a period of time in which to be received. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] + public sealed class TimeToBeReceivedAttribute : Attribute + { + /// + /// Sets the time to be received. + /// + /// A timeSpan that can be interpreted by . + public TimeToBeReceivedAttribute(string timeSpan) + { + Guard.AgainstNullAndEmpty(nameof(timeSpan), timeSpan); + TimeSpan parsed; + if (!TimeSpan.TryParse(timeSpan, out parsed)) + { + var error = $"Could not parse '{timeSpan}' as a timespan."; + throw new ArgumentException(error); + } + Guard.AgainstNegativeAndZero(nameof(timeSpan), parsed); + TimeToBeReceived = parsed; + } + + /// + /// Gets the maximum time in which a message must be received. + /// + /// + /// If the interval specified by the property expires before the message + /// is received by the destination of the message the message will automatically be canceled. + /// + public TimeSpan TimeToBeReceived { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedConventionExtensions.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedConventionExtensions.cs new file mode 100644 index 00000000000..32e7ca6c97a --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedConventionExtensions.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System; + + /// + /// Provides the ability to specify TTBR using a convention. + /// + public static class TimeToBeReceivedConventionExtensions + { + /// + /// Sets the function to be used to evaluate whether a message has a time to be received. + /// + public static ConventionsBuilder DefiningTimeToBeReceivedAs(this ConventionsBuilder builder, Func retrieveTimeToBeReceived) + { + Guard.AgainstNull(nameof(builder), builder); + Guard.AgainstNull(nameof(retrieveTimeToBeReceived), retrieveTimeToBeReceived); + + builder.Settings.Set(new UserDefinedTimeToBeReceivedConvention(retrieveTimeToBeReceived)); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedMappings.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedMappings.cs new file mode 100644 index 00000000000..4e5842a8c26 --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/TimeToBeReceivedMappings.cs @@ -0,0 +1,65 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + + class TimeToBeReceivedMappings + { + public TimeToBeReceivedMappings(IEnumerable knownMessages, Func convention, bool doesTransportSupportDiscardIfNotReceivedBefore) + { + this.doesTransportSupportDiscardIfNotReceivedBefore = doesTransportSupportDiscardIfNotReceivedBefore; + this.convention = convention; + + mappings = new ConcurrentDictionary(); + + foreach (var messageType in knownMessages) + { + mappings[messageType] = GetTimeToBeReceived(convention, messageType, doesTransportSupportDiscardIfNotReceivedBefore); + } + } + + public bool TryGetTimeToBeReceived(Type messageType, out TimeSpan timeToBeReceived) + { + timeToBeReceived = mappings.GetOrAdd(messageType, type => GetTimeToBeReceived(convention, type, doesTransportSupportDiscardIfNotReceivedBefore)); + return timeToBeReceived != TimeSpan.MaxValue; + } + + // ReSharper disable once UnusedParameter.Local + static TimeSpan GetTimeToBeReceived(Func convention, Type messageType, bool doesTransportSupportDiscardIfNotReceivedBefore) + { + var timeToBeReceived = convention(messageType); + + if (timeToBeReceived < TimeSpan.MaxValue && !doesTransportSupportDiscardIfNotReceivedBefore) + { + throw new Exception("Messages with TimeToBeReceived found but the selected transport does not support this type of restriction. Remove TTBR from messages or select a transport that does support TTBR"); + } + + if (timeToBeReceived <= TimeSpan.Zero) + { + throw new Exception("TimeToBeReceived must be greater that 0"); + } + return timeToBeReceived; + } + + ConcurrentDictionary mappings; + + Func convention; + + bool doesTransportSupportDiscardIfNotReceivedBefore; + + public static Func DefaultConvention = t => + { + var timeToBeReceived = TimeSpan.MaxValue; + foreach (var customAttribute in t.GetCustomAttributes(typeof(TimeToBeReceivedAttribute), true)) + { + var attribute = customAttribute as TimeToBeReceivedAttribute; + if (attribute != null) + { + timeToBeReceived = attribute.TimeToBeReceived; + } + } + return timeToBeReceived; + }; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Performance/TimeToBeReceived/UserDefinedTimeToBeReceivedConvention.cs b/src/NServiceBus.Core/Performance/TimeToBeReceived/UserDefinedTimeToBeReceivedConvention.cs new file mode 100644 index 00000000000..aabeb0d260d --- /dev/null +++ b/src/NServiceBus.Core/Performance/TimeToBeReceived/UserDefinedTimeToBeReceivedConvention.cs @@ -0,0 +1,14 @@ +namespace NServiceBus +{ + using System; + + class UserDefinedTimeToBeReceivedConvention + { + public UserDefinedTimeToBeReceivedConvention(Func retrieveTimeToBeReceived) + { + GetTimeToBeReceivedForMessage = retrieveTimeToBeReceived; + } + + public Func GetTimeToBeReceivedForMessage { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/CompletableSynchronizedStorageSession.cs b/src/NServiceBus.Core/Persistence/CompletableSynchronizedStorageSession.cs new file mode 100644 index 00000000000..9389a221feb --- /dev/null +++ b/src/NServiceBus.Core/Persistence/CompletableSynchronizedStorageSession.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Persistence +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents a storage session from point of view of the infrastructure. + /// + public interface CompletableSynchronizedStorageSession : SynchronizedStorageSession, IDisposable + { + /// + /// Completes the session by saving the changes. + /// + Task CompleteAsync(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs index 3339f950765..cdda4c325e0 100644 --- a/src/NServiceBus.Core/Persistence/EnabledPersistence.cs +++ b/src/NServiceBus.Core/Persistence/EnabledPersistence.cs @@ -1,11 +1,11 @@ -namespace NServiceBus.Persistence +namespace NServiceBus { using System; using System.Collections.Generic; class EnabledPersistence { - public Type DefinitionType; public List SelectedStorages { get; set; } + public Type DefinitionType; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/ISynchronizedStorage.cs b/src/NServiceBus.Core/Persistence/ISynchronizedStorage.cs new file mode 100644 index 00000000000..db116bf3b3d --- /dev/null +++ b/src/NServiceBus.Core/Persistence/ISynchronizedStorage.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Persistence +{ + using System.Threading.Tasks; + using Extensibility; + + /// + /// Represents a storage to which the writes are synchronized with message receiving i.e. message receive is acknowledged + /// only if data has been successfully saved. + /// + public interface ISynchronizedStorage + { + /// + /// Begins a new storage session which is an atomic unit of work. + /// + /// The context information. + Task OpenSession(ContextBag contextBag); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/ISynchronizedStorageAdapter.cs b/src/NServiceBus.Core/Persistence/ISynchronizedStorageAdapter.cs new file mode 100644 index 00000000000..da244c1dce0 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/ISynchronizedStorageAdapter.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Persistence +{ + using System.Threading.Tasks; + using Extensibility; + using Outbox; + using Transport; + + /// + /// Converts the outbox transaction into a synchronized storage session if possible. + /// + public interface ISynchronizedStorageAdapter + { + /// + /// Returns a synchronized storage session based on the outbox transaction if possible. + /// + /// Outbox transaction. + /// Context. + /// Session or null, if unable to adapt. + Task TryAdapt(OutboxTransaction transaction, ContextBag context); + + /// + /// Returns a synchronized storage session based on the outbox transaction if possible. + /// + /// Transport transaction. + /// Context. + /// Session or null, if unable to adapt. + Task TryAdapt(TransportTransaction transportTransaction, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayDeduplication.cs b/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayDeduplication.cs index 514603fd74a..d83139c77a5 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayDeduplication.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayDeduplication.cs @@ -1,23 +1,47 @@ -namespace NServiceBus.Gateway.Deduplication +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using Gateway.Deduplication; class InMemoryGatewayDeduplication : IDeduplicateMessages { - public bool DeduplicateMessage(string clientId, DateTime timeReceived) + public Task DeduplicateMessage(string clientId, DateTime timeReceived, ContextBag context) { lock (persistence) { var item = persistence.SingleOrDefault(m => m.Id == clientId); if (item != null) - return false; + { + return TaskEx.FalseTask; + } - return persistence.Add(new GatewayMessage { Id = clientId, TimeReceived = timeReceived }); + return Task.FromResult(persistence.Add(new GatewayMessage + { + Id = clientId, + TimeReceived = timeReceived + })); } } + public int DeleteDeliveredMessages(DateTime until) + { + int count; + lock (persistence) + { + var items = persistence.Where(msg => msg.TimeReceived <= until).ToList(); + count = items.Count; + + items.ForEach(item => persistence.Remove(item)); + } + return count; + } + + ISet persistence = new HashSet(new MessageDataComparer()); + class MessageDataComparer : IEqualityComparer { public bool Equals(GatewayMessage x, GatewayMessage y) @@ -31,20 +55,6 @@ public int GetHashCode(GatewayMessage obj) } } - readonly ISet persistence = new HashSet(new MessageDataComparer()); - - public int DeleteDeliveredMessages(DateTime until) - { - int count; - lock (persistence) - { - var items = persistence.Where(msg => msg.TimeReceived <= until).ToList(); - count = items.Count(); - - items.ForEach(item => persistence.Remove(item)); - } - return count; - } class GatewayMessage { public string Id { get; set; } diff --git a/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayPersistence.cs b/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayPersistence.cs index af9ddc9e97d..93dff6def0e 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayPersistence.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/Gateway/InMemoryGatewayPersistence.cs @@ -1,19 +1,17 @@ namespace NServiceBus.Features { - using NServiceBus.Gateway.Deduplication; - /// - /// In-memory Gateway + /// In-memory Gateway. /// public class InMemoryGatewayPersistence : Feature { internal InMemoryGatewayPersistence() { - DependsOn("Gateway"); + DependsOn("NServiceBus.Features.Gateway"); } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemory.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemory.cs index 806c519b130..ef1f7c1fe2d 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/InMemory.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemory.cs @@ -1,7 +1,7 @@ namespace NServiceBus { - using NServiceBus.Features; - using NServiceBus.Persistence; + using Features; + using Persistence; /// /// Used to enable InMemory persistence. diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemorySynchronizedStorage.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemorySynchronizedStorage.cs new file mode 100644 index 00000000000..8c37b128bcc --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemorySynchronizedStorage.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Extensibility; + using Persistence; + + class InMemorySynchronizedStorage : ISynchronizedStorage + { + public Task OpenSession(ContextBag contextBag) + { + var session = (CompletableSynchronizedStorageSession) new InMemorySynchronizedStorageSession(); + return Task.FromResult(session); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransaction.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransaction.cs new file mode 100644 index 00000000000..cf6d7d8fa6c --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransaction.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + + class InMemoryTransaction + { + public void Enlist(Action action) + { + actions.Add(action); + } + + public void Commit() + { + foreach (var action in actions) + { + action(); + } + actions.Clear(); + } + + public void Rollback() + { + actions.Clear(); + } + + List actions = new List(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageFeature.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageFeature.cs new file mode 100644 index 00000000000..98691e02a2c --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageFeature.cs @@ -0,0 +1,16 @@ +namespace NServiceBus +{ + using Features; + + class InMemoryTransactionalStorageFeature : Feature + { + /// + /// Called when the features is activated. + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageSession.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageSession.cs new file mode 100644 index 00000000000..06554b04a81 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalStorageSession.cs @@ -0,0 +1,45 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Janitor; + using Persistence; + + [SkipWeaving] + class InMemorySynchronizedStorageSession : CompletableSynchronizedStorageSession + { + public InMemorySynchronizedStorageSession(InMemoryTransaction transaction) + { + Transaction = transaction; + } + + public InMemorySynchronizedStorageSession() + : this(new InMemoryTransaction()) + { + ownsTransaction = true; + } + + public InMemoryTransaction Transaction { get; private set; } + + public void Dispose() + { + Transaction = null; + } + + public Task CompleteAsync() + { + if (ownsTransaction) + { + Transaction.Commit(); + } + return TaskEx.CompletedTask; + } + + public void Enlist(Action action) + { + Transaction.Enlist(action); + } + + bool ownsTransaction; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalSynchronizedStorageAdapter.cs b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalSynchronizedStorageAdapter.cs new file mode 100644 index 00000000000..daf2e3a0854 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/InMemoryTransactionalSynchronizedStorageAdapter.cs @@ -0,0 +1,79 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using Extensibility; + using Outbox; + using Persistence; + using Transport; + + class InMemoryTransactionalSynchronizedStorageAdapter : ISynchronizedStorageAdapter + { + public Task TryAdapt(OutboxTransaction transaction, ContextBag context) + { + var inMemOutboxTransaction = transaction as InMemoryOutboxTransaction; + if (inMemOutboxTransaction != null) + { + CompletableSynchronizedStorageSession session = new InMemorySynchronizedStorageSession(inMemOutboxTransaction.Transaction); + return Task.FromResult(session); + } + return EmptyTask; + } + + public Task TryAdapt(TransportTransaction transportTransaction, ContextBag context) + { + Transaction ambientTransaction; + + if (transportTransaction.TryGet(out ambientTransaction)) + { + var transaction = new InMemoryTransaction(); + CompletableSynchronizedStorageSession session = new InMemorySynchronizedStorageSession(transaction); + ambientTransaction.EnlistVolatile(new EnlistmentNotification(transaction), EnlistmentOptions.None); + return Task.FromResult(session); + } + return EmptyTask; + } + + static readonly Task EmptyTask = Task.FromResult(null); + + class EnlistmentNotification : IEnlistmentNotification + { + public EnlistmentNotification(InMemoryTransaction transaction) + { + this.transaction = transaction; + } + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + try + { + transaction.Commit(); + preparingEnlistment.Prepared(); + } + catch (Exception ex) + { + preparingEnlistment.ForceRollback(ex); + } + } + + public void Commit(Enlistment enlistment) + { + enlistment.Done(); + } + + public void Rollback(Enlistment enlistment) + { + transaction.Rollback(); + enlistment.Done(); + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + + InMemoryTransaction transaction; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySagaPersister.cs b/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySagaPersister.cs deleted file mode 100644 index 0f2770e9e43..00000000000 --- a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySagaPersister.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5.0", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static class ConfigureInMemorySagaPersister - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5.0", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static Configure InMemorySagaPersister(this Configure config) - { - throw new InvalidOperationException(); - } - } -} diff --git a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySubscriptionStorage.cs b/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySubscriptionStorage.cs deleted file mode 100644 index 33d71513ccd..00000000000 --- a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemorySubscriptionStorage.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5.0", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static class ConfigureInMemorySubscriptionStorage - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5.0", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static Configure InMemorySubscriptionStorage(this Configure config) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemoryTimeoutPersister.cs b/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemoryTimeoutPersister.cs deleted file mode 100644 index 75ad00a7542..00000000000 --- a/src/NServiceBus.Core/Persistence/InMemory/Obsoletes/ConfigureInMemoryTimeoutPersister.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable once UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static class ConfigureInMemoryTimeoutPersister - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UsePersistence()`, where configuration is an instance of type `BusConfiguration`.")] - public static Configure UseInMemoryTimeoutPersister(this Configure config) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxPersistence.cs b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxPersistence.cs index fe76b333f4b..efaca0344a1 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxPersistence.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxPersistence.cs @@ -2,7 +2,8 @@ { using System; using System.Threading; - using NServiceBus.InMemory.Outbox; + using System.Threading.Tasks; + using NServiceBus.Outbox; /// /// Used to configure in memory outbox persistence. @@ -12,45 +13,58 @@ public class InMemoryOutboxPersistence : Feature internal InMemoryOutboxPersistence() { DependsOn(); - RegisterStartupTask(); + Defaults(s => s.EnableFeature(typeof(InMemoryTransactionalStorageFeature))); } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(t => t.TimeToKeepDeduplicationData, context.Settings.Get(Outbox.TimeToKeepDeduplicationEntries)); + var outboxStorage = new InMemoryOutboxStorage(); + context.Container.RegisterSingleton(outboxStorage); + + var timeSpan = context.Settings.Get(TimeToKeepDeduplicationEntries); + + context.RegisterStartupTask(new OutboxCleaner(outboxStorage, timeSpan)); } + internal const string TimeToKeepDeduplicationEntries = "Outbox.TimeToKeepDeduplicationEntries"; + class OutboxCleaner : FeatureStartupTask { - public InMemoryOutboxStorage InMemoryOutboxStorage { get; set; } - - public TimeSpan TimeToKeepDeduplicationData { get; set; } + public OutboxCleaner(InMemoryOutboxStorage storage, TimeSpan timeToKeepDeduplicationData) + { + this.timeToKeepDeduplicationData = timeToKeepDeduplicationData; + inMemoryOutboxStorage = storage; + } - protected override void OnStart() + protected override Task OnStart(IMessageSession session) { cleanupTimer = new Timer(PerformCleanup, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + return TaskEx.CompletedTask; } - protected override void OnStop() + protected override Task OnStop(IMessageSession session) { using (var waitHandle = new ManualResetEvent(false)) { cleanupTimer.Dispose(waitHandle); + // TODO: Use async synchronization primitive waitHandle.WaitOne(); } + return TaskEx.CompletedTask; } void PerformCleanup(object state) { - InMemoryOutboxStorage.RemoveEntriesOlderThan(DateTime.UtcNow - TimeToKeepDeduplicationData); + inMemoryOutboxStorage.RemoveEntriesOlderThan(DateTime.UtcNow - timeToKeepDeduplicationData); } + readonly InMemoryOutboxStorage inMemoryOutboxStorage; + readonly TimeSpan timeToKeepDeduplicationData; + // ReSharper disable once NotAccessedField.Local Timer cleanupTimer; } diff --git a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxSettingsExtensions.cs b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxSettingsExtensions.cs new file mode 100644 index 00000000000..b872fdc22f7 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxSettingsExtensions.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.InMemory.Outbox +{ + using System; + using Configuration.AdvanceExtensibility; + using Features; + using NServiceBus.Outbox; + + /// + /// Contains InMemoryOutbox related settings extensions. + /// + public static class InMemoryOutboxSettingsExtensions + { + /// + /// Specifies how long the outbox should keep message data in storage to be able to deduplicate. + /// + /// The outbox settings. + /// + /// Defines the timespan which indicates how long the outbox deduplication entries should be kept. + /// i.e. if TimeSpan.FromDays(1) is used the deduplication entries are kept for no longer than one day. + /// It is not possible to use a negative or zero TimeSpan value. + /// + public static OutboxSettings TimeToKeepDeduplicationData(this OutboxSettings settings, TimeSpan time) + { + Guard.AgainstNegativeAndZero(nameof(time), time); + settings.GetSettings().Set(InMemoryOutboxPersistence.TimeToKeepDeduplicationEntries, time); + return settings; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxStorage.cs b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxStorage.cs index 5d0c5a46664..f655ee8a509 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxStorage.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxStorage.cs @@ -1,70 +1,94 @@ -namespace NServiceBus.InMemory.Outbox +namespace NServiceBus { using System; using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using NServiceBus.Outbox; + using System.Threading.Tasks; + using Extensibility; + using Outbox; class InMemoryOutboxStorage : IOutboxStorage { - public bool TryGet(string messageId, out OutboxMessage message) + public Task Get(string messageId, ContextBag context) { StoredMessage storedMessage; - message = null; - if (!storage.TryGetValue(messageId, out storedMessage)) { - return false; + return NoOutboxMessageTask; } - message = new OutboxMessage(messageId); - message.TransportOperations.AddRange(storedMessage.TransportOperations); + return Task.FromResult(new OutboxMessage(messageId, storedMessage.TransportOperations)); + } - return true; + public Task BeginTransaction(ContextBag context) + { + return Task.FromResult(new InMemoryOutboxTransaction()); } - public void Store(string messageId, IEnumerable transportOperations) + public Task Store(OutboxMessage message, OutboxTransaction transaction, ContextBag context) { - if (!storage.TryAdd(messageId, new StoredMessage(messageId, transportOperations.ToList()))) + var tx = (InMemoryOutboxTransaction) transaction; + tx.Enlist(() => { - throw new Exception(string.Format("Outbox message with id '{0}' is already present in storage.", messageId)); - } + if (!storage.TryAdd(message.MessageId, new StoredMessage(message.MessageId, message.TransportOperations))) + { + throw new Exception($"Outbox message with id '{message.MessageId}' is already present in storage."); + } + }); + return TaskEx.CompletedTask; } - public void SetAsDispatched(string messageId) + public Task SetAsDispatched(string messageId, ContextBag context) { StoredMessage storedMessage; if (!storage.TryGetValue(messageId, out storedMessage)) { - return; + return TaskEx.CompletedTask; } - storedMessage.TransportOperations.Clear(); - storedMessage.Dispatched = true; + storedMessage.MarkAsDispatched(); + return TaskEx.CompletedTask; + } + + public void RemoveEntriesOlderThan(DateTime dateTime) + { + foreach (var entry in storage) + { + var storedMessage = entry.Value; + if (storedMessage.Dispatched && storedMessage.StoredAt < dateTime) + { + StoredMessage toRemove; + + storage.TryRemove(entry.Key, out toRemove); + } + } } + ConcurrentDictionary storage = new ConcurrentDictionary(); + static Task NoOutboxMessageTask = Task.FromResult(default(OutboxMessage)); class StoredMessage { - public StoredMessage(string messageId, IList transportOperations) + public StoredMessage(string messageId, TransportOperation[] transportOperations) { - this.transportOperations = transportOperations; + TransportOperations = transportOperations; Id = messageId; StoredAt = DateTime.UtcNow; } - public string Id { get; private set; } + public string Id { get; } + + public bool Dispatched { get; private set; } - public bool Dispatched { get; set; } + public DateTime StoredAt { get; } - public DateTime StoredAt { get; set; } + public TransportOperation[] TransportOperations { get; private set; } - public IList TransportOperations + public void MarkAsDispatched() { - get { return transportOperations; } + Dispatched = true; + TransportOperations = new TransportOperation[0]; } protected bool Equals(StoredMessage other) @@ -86,33 +110,16 @@ public override bool Equals(object obj) { return false; } - return Equals((StoredMessage)obj); + return Equals((StoredMessage) obj); } public override int GetHashCode() { unchecked { - return ((Id != null ? Id.GetHashCode() : 0) * 397) ^ Dispatched.GetHashCode(); + return ((Id?.GetHashCode() ?? 0)*397) ^ Dispatched.GetHashCode(); } } - - readonly IList transportOperations; - } - - public void RemoveEntriesOlderThan(DateTime dateTime) - { - var entriesToRemove = storage - .Where(e => e.Value.Dispatched && e.Value.StoredAt < dateTime) - .Select(e => e.Key) - .ToList(); - - foreach (var entry in entriesToRemove) - { - StoredMessage toRemove; - - storage.TryRemove(entry, out toRemove); - } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxTransaction.cs b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxTransaction.cs new file mode 100644 index 00000000000..d644a8ade66 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/InMemory/Outbox/InMemoryOutboxTransaction.cs @@ -0,0 +1,34 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Janitor; + using Outbox; + + [SkipWeaving] + class InMemoryOutboxTransaction : OutboxTransaction + { + public InMemoryOutboxTransaction() + { + Transaction = new InMemoryTransaction(); + } + + public InMemoryTransaction Transaction { get; private set; } + + public void Dispose() + { + Transaction = null; + } + + public Task Commit() + { + Transaction.Commit(); + return TaskEx.CompletedTask; + } + + public void Enlist(Action action) + { + Transaction.Enlist(action); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersistence.cs b/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersistence.cs index c0148987995..476a4535d41 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersistence.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersistence.cs @@ -1,7 +1,5 @@ namespace NServiceBus.Features { - using NServiceBus.InMemory.SagaPersister; - /// /// Used to configure in memory saga persistence. /// @@ -10,10 +8,11 @@ public class InMemorySagaPersistence : Feature internal InMemorySagaPersistence() { DependsOn(); + Defaults(s => s.EnableFeature(typeof(InMemoryTransactionalStorageFeature))); } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { diff --git a/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersister.cs b/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersister.cs index 458e43eabd0..552b2d5f926 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersister.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/SagaPersister/InMemorySagaPersister.cs @@ -1,34 +1,35 @@ -namespace NServiceBus.InMemory.SagaPersister +namespace NServiceBus { using System; using System.Collections.Concurrent; + using System.Collections.Generic; using System.Runtime.CompilerServices; - using NServiceBus.Saga; - using NServiceBus.Sagas; - using NServiceBus.Serializers.Json; + using System.Threading.Tasks; + using Extensibility; + using Persistence; + using Sagas; - /// - /// In memory implementation of ISagaPersister for quick development. - /// class InMemorySagaPersister : ISagaPersister { - public InMemorySagaPersister(SagaMetaModel sagaModel) + public Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { - this.sagaModel = sagaModel; - } - - public void Complete(IContainSagaData saga) - { - VersionedSagaEntity value; - if (data.TryRemove(saga.Id, out value)) + var inMemSession = (InMemorySynchronizedStorageSession) session; + inMemSession.Enlist(() => { - object lockToken; - lockers.TryRemove(value.LockTokenKey, out lockToken); - } + VersionedSagaEntity value; + if (data.TryRemove(sagaData.Id, out value)) + { + object lockToken; + lockers.TryRemove(value.LockTokenKey, out lockToken); + } + }); + return TaskEx.CompletedTask; } - public TSagaData Get(string propertyName, object propertyValue) where TSagaData : IContainSagaData + public Task Get(string propertyName, object propertyValue, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData { + Guard.AgainstNull(nameof(propertyValue), propertyValue); + foreach (var entity in data.Values) { if (!(entity.SagaData is TSagaData)) @@ -41,92 +42,99 @@ public TSagaData Get(string propertyName, object propertyValue) where { continue; } - var existingValue = prop.GetValue(entity.SagaData); if (existingValue.ToString() != propertyValue.ToString()) { continue; } - var sagaData = entity.Read(); - return sagaData; + return Task.FromResult(sagaData); } - return default(TSagaData); + return Task.FromResult(default(TSagaData)); } - public TSagaData Get(Guid sagaId) where TSagaData : IContainSagaData + public Task Get(Guid sagaId, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData { VersionedSagaEntity result; if (data.TryGetValue(sagaId, out result) && result?.SagaData is TSagaData) { var sagaData = result.Read(); - return sagaData; + return Task.FromResult(sagaData); } - return default(TSagaData); + return Task.FromResult(default(TSagaData)); } - public void Save(IContainSagaData saga) + public Task Save(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SynchronizedStorageSession session, ContextBag context) { - var lockenTokenKey = $"{saga.GetType().FullName}"; - var lockToken = lockers.GetOrAdd(lockenTokenKey, key => new object()); - lock (lockToken) + var inMemSession = (InMemorySynchronizedStorageSession) session; + inMemSession.Enlist(() => { - ValidateUniqueProperties(saga); + var lockenTokenKey = $"{sagaData.GetType().FullName}.{correlationProperty?.Name ?? "None"}.{correlationProperty?.Value ?? "None"}"; + var lockToken = lockers.GetOrAdd(lockenTokenKey, key => new object()); + lock (lockToken) + { + if (correlationProperty != SagaCorrelationProperty.None) + { + ValidateUniqueProperties(correlationProperty, sagaData); + } - data.AddOrUpdate(saga.Id, - id => new VersionedSagaEntity(saga, lockenTokenKey), - (id, original) => new VersionedSagaEntity(saga, lockenTokenKey, original)); // we can never end up here. - } + data.AddOrUpdate(sagaData.Id, + id => new VersionedSagaEntity(sagaData, lockenTokenKey), + (id, original) => new VersionedSagaEntity(sagaData, lockenTokenKey, original)); // we can never end up here. + } + }); + return TaskEx.CompletedTask; } - public void Update(IContainSagaData saga) + public Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { - Save(saga); + var inMemSession = (InMemorySynchronizedStorageSession) session; + inMemSession.Enlist(() => + { + data.AddOrUpdate(sagaData.Id, + id => new VersionedSagaEntity(sagaData, $"{sagaData.GetType().FullName}.None.None"), // we can never end up here. + (id, original) => new VersionedSagaEntity(sagaData, original.LockTokenKey, original)); + }); + return TaskEx.CompletedTask; } - void ValidateUniqueProperties(IContainSagaData saga) + void ValidateUniqueProperties(SagaCorrelationProperty correlationProperty, IContainSagaData saga) { var sagaType = saga.GetType(); - var sagaMetaData = sagaModel.FindByEntityName(sagaType.FullName); - - if (sagaMetaData.CorrelationProperties.Count == 0) return; - + var existingSagas = new List(); // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var storedSaga in data) + foreach (var s in data) { - if (storedSaga.Value.SagaData.GetType() != sagaType || (storedSaga.Key == saga.Id)) + if (s.Value.SagaData.GetType() == sagaType && (s.Key != saga.Id)) { - continue; + existingSagas.Add(s.Value); } + } + var uniqueProperty = sagaType.GetProperty(correlationProperty.Name); - foreach (var correlationProperty in sagaMetaData.CorrelationProperties) - { - var uniqueProperty = saga.GetType().GetProperty(correlationProperty.Name); - if (!uniqueProperty.CanRead) - { - continue; - } + if (correlationProperty.Value == null) + { + var message = $"Cannot store saga with id '{saga.Id}' since the unique property '{uniqueProperty.Name}' has a null value."; + throw new InvalidOperationException(message); + } - var inComingSagaPropertyValue = uniqueProperty.GetValue(saga, null); - var storedSagaPropertyValue = uniqueProperty.GetValue(storedSaga.Value.SagaData, null); - if (inComingSagaPropertyValue.Equals(storedSagaPropertyValue)) - { - var message = $"Cannot store a saga. The saga with id '{storedSaga.Value.SagaData.Id}' already has property '{uniqueProperty}' with value '{storedSagaPropertyValue}'."; - throw new InvalidOperationException(message); - } + foreach (var storedSaga in existingSagas) + { + var storedSagaPropertyValue = uniqueProperty.GetValue(storedSaga.SagaData, null); + if (Equals(correlationProperty.Value, storedSagaPropertyValue)) + { + var message = $"Cannot store a saga. The saga with id '{storedSaga.SagaData.Id}' already has property '{uniqueProperty.Name}'."; + throw new InvalidOperationException(message); } } } - readonly SagaMetaModel sagaModel; ConcurrentDictionary data = new ConcurrentDictionary(); ConcurrentDictionary lockers = new ConcurrentDictionary(); class VersionedSagaEntity { - static JsonMessageSerializer serializer = new JsonMessageSerializer(null); - public VersionedSagaEntity(IContainSagaData sagaData, string lockTokenKey, VersionedSagaEntity original = null) { LockTokenKey = lockTokenKey; @@ -174,13 +182,14 @@ static IContainSagaData DeepClone(IContainSagaData source) return (IContainSagaData) serializer.DeserializeObject(json, source.GetType()); } + public IContainSagaData SagaData; public string LockTokenKey; - public IContainSagaData SagaData; + ConditionalWeakTable versionCache; int version; - ConditionalWeakTable versionCache; + static JsonMessageSerializer serializer = new JsonMessageSerializer(null); class SagaVersion { diff --git a/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionPersistence.cs b/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionPersistence.cs index cb1cb676426..25cf545f665 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionPersistence.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionPersistence.cs @@ -1,7 +1,5 @@ namespace NServiceBus.Features { - using NServiceBus.InMemory.SubscriptionStorage; - /// /// Used to configure in memory subscription persistence. /// @@ -13,7 +11,7 @@ internal InMemorySubscriptionPersistence() } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { diff --git a/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionStorage.cs b/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionStorage.cs index eb1223565f8..79a7e7f77d3 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionStorage.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/SubscriptionStorage/InMemorySubscriptionStorage.cs @@ -1,59 +1,48 @@ -namespace NServiceBus.InMemory.SubscriptionStorage +namespace NServiceBus { using System; using System.Collections.Concurrent; using System.Collections.Generic; - using NServiceBus.Unicast.Subscriptions; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; + using System.Threading.Tasks; + using Extensibility; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; - /// - /// In memory implementation of the subscription storage - /// class InMemorySubscriptionStorage : ISubscriptionStorage { - void ISubscriptionStorage.Subscribe(Address address, IEnumerable messageTypes) + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { - foreach (var m in messageTypes) - { - var dict = storage.GetOrAdd(m, type => new ConcurrentDictionary()); + var dict = storage.GetOrAdd(messageType, type => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase)); - dict.AddOrUpdate(address, addValueFactory, updateValueFactory); - } + dict.AddOrUpdate(subscriber.TransportAddress, _ => subscriber, (_, __) => subscriber); + return TaskEx.CompletedTask; } - void ISubscriptionStorage.Unsubscribe(Address address, IEnumerable messageTypes) + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { - foreach (var m in messageTypes) + ConcurrentDictionary dict; + if (storage.TryGetValue(messageType, out dict)) { - ConcurrentDictionary dict; - if (storage.TryGetValue(m, out dict)) - { - object _; - dict.TryRemove(address, out _); - } + Subscriber _; + dict.TryRemove(subscriber.TransportAddress, out _); } + return TaskEx.CompletedTask; } - IEnumerable
ISubscriptionStorage.GetSubscriberAddressesForMessage(IEnumerable messageTypes) + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) { - var result = new HashSet
(); + var result = new HashSet(); foreach (var m in messageTypes) { - ConcurrentDictionary list; + ConcurrentDictionary list; if (storage.TryGetValue(m, out list)) { - result.UnionWith(list.Keys); + result.UnionWith(list.Values); } } - return result; - } - - public void Init() - { + return Task.FromResult((IEnumerable) result); } - readonly ConcurrentDictionary> storage = new ConcurrentDictionary>(); - Func addValueFactory = a => null; - Func updateValueFactory = (a, o) => null; + ConcurrentDictionary> storage = new ConcurrentDictionary>(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersistence.cs b/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersistence.cs index f2febd5e2c2..c53d2fc7f95 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersistence.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersistence.cs @@ -1,6 +1,6 @@ namespace NServiceBus.Features { - using NServiceBus.InMemory.TimeoutPersister; + using System; /// /// Used to configure in memory timeout persistence. @@ -13,11 +13,11 @@ internal InMemoryTimeoutPersistence() } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + context.Container.ConfigureComponent(_ => new InMemoryTimeoutPersister(() => DateTime.UtcNow), DependencyLifecycle.SingleInstance); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersister.cs b/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersister.cs index 300684975fe..ac521f92463 100644 --- a/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersister.cs +++ b/src/NServiceBus.Core/Persistence/InMemory/TimeoutPersister/InMemoryTimeoutPersister.cs @@ -1,65 +1,54 @@ -namespace NServiceBus.InMemory.TimeoutPersister +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; using System.Threading; + using System.Threading.Tasks; + using Extensibility; using Timeout.Core; - class InMemoryTimeoutPersister : IPersistTimeouts, IPersistTimeoutsV2 + class InMemoryTimeoutPersister : IPersistTimeouts, IQueryTimeouts, IDisposable { - List storage = new List(); - ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); - - public IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery) + public InMemoryTimeoutPersister(Func currentTimeProvider) { - var now = DateTime.UtcNow; - nextTimeToRunQuery = DateTime.MaxValue; + this.currentTimeProvider = currentTimeProvider; + } - var tuples = new List>(); + public void Dispose() + { + } + public Task Add(TimeoutData timeout, ContextBag context) + { + timeout.Id = Guid.NewGuid().ToString(); try { - readerWriterLock.EnterReadLock(); - - foreach (var data in storage) - { - if (data.Time > now && data.Time < nextTimeToRunQuery) - { - nextTimeToRunQuery = data.Time; - } - if (data.Time > startSlice && data.Time <= now) - { - tuples.Add(new Tuple(data.Id, data.Time)); - } - } + readerWriterLock.EnterWriteLock(); + storage.Add(timeout); } finally { - readerWriterLock.ExitReadLock(); - } - if (nextTimeToRunQuery == DateTime.MaxValue) - { - nextTimeToRunQuery = now.AddMinutes(1); + readerWriterLock.ExitWriteLock(); } - return tuples; + + return TaskEx.CompletedTask; } - public void Add(TimeoutData timeout) + public Task Peek(string timeoutId, ContextBag context) { - timeout.Id = Guid.NewGuid().ToString(); try { - readerWriterLock.EnterWriteLock(); - storage.Add(timeout); + readerWriterLock.EnterReadLock(); + return Task.FromResult(storage.SingleOrDefault(t => t.Id.ToString() == timeoutId)); } finally { - readerWriterLock.ExitWriteLock(); + readerWriterLock.ExitReadLock(); } } - public bool TryRemove(string timeoutId, out TimeoutData timeoutData) + public Task TryRemove(string timeoutId, ContextBag context) { try { @@ -70,14 +59,12 @@ public bool TryRemove(string timeoutId, out TimeoutData timeoutData) var data = storage[index]; if (data.Id == timeoutId) { - timeoutData = data; storage.RemoveAt(index); - return true; + return TaskEx.TrueTask; } } - timeoutData = null; - return false; + return TaskEx.FalseTask; } finally { @@ -85,12 +72,12 @@ public bool TryRemove(string timeoutId, out TimeoutData timeoutData) } } - public void RemoveTimeoutBy(Guid sagaId) + public Task RemoveTimeoutBy(Guid sagaId, ContextBag context) { try { readerWriterLock.EnterWriteLock(); - for (var index = 0; index < storage.Count; ) + for (var index = 0; index < storage.Count;) { var timeoutData = storage[index]; if (timeoutData.SagaId == sagaId) @@ -105,26 +92,48 @@ public void RemoveTimeoutBy(Guid sagaId) { readerWriterLock.ExitWriteLock(); } + + return TaskEx.CompletedTask; } - public TimeoutData Peek(string timeoutId) + public Task GetNextChunk(DateTime startSlice) { + var now = currentTimeProvider(); + var nextTimeToRunQuery = DateTime.MaxValue; + var dueTimeouts = new List(); + try { readerWriterLock.EnterReadLock(); - return storage.SingleOrDefault(t => t.Id == timeoutId); + + foreach (var data in storage) + { + if (data.Time > now && data.Time < nextTimeToRunQuery) + { + nextTimeToRunQuery = data.Time; + } + if (data.Time > startSlice && data.Time <= now) + { + dueTimeouts.Add(new TimeoutsChunk.Timeout(data.Id, data.Time)); + } + } } finally { readerWriterLock.ExitReadLock(); } - } + if (nextTimeToRunQuery == DateTime.MaxValue) + { + nextTimeToRunQuery = now.Add(EmptyResultsNextTimeToRunQuerySpan); + } - public bool TryRemove(string timeoutId) - { - TimeoutData timeoutData; - return TryRemove(timeoutId, out timeoutData); + return Task.FromResult(new TimeoutsChunk(dueTimeouts.ToArray(), nextTimeToRunQuery)); } + + Func currentTimeProvider; + ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); + List storage = new List(); + public static TimeSpan EmptyResultsNextTimeToRunQuerySpan = TimeSpan.FromMinutes(1); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/Msmq.cs b/src/NServiceBus.Core/Persistence/Msmq/Msmq.cs index cdabb117cac..fb9c9b4ca29 100644 --- a/src/NServiceBus.Core/Persistence/Msmq/Msmq.cs +++ b/src/NServiceBus.Core/Persistence/Msmq/Msmq.cs @@ -1,6 +1,6 @@ namespace NServiceBus.Persistence.Legacy { - using NServiceBus.Features; + using Features; /// /// Used to enable Msmq persistence. diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/ConfigureMsmqSubscriptionStorage_Obsolete.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/ConfigureMsmqSubscriptionStorage_Obsolete.cs deleted file mode 100644 index 33b8f019e6d..00000000000 --- a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/ConfigureMsmqSubscriptionStorage_Obsolete.cs +++ /dev/null @@ -1,38 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UsePersistence()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static class ConfigureMsmqSubscriptionStorage - { - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use configuration.UsePersistence(), where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.")] - public static Configure MsmqSubscriptionStorage(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Use `configuration.UsePersistence()`, where `configuration` is an instance of type `BusConfiguration` and assign the queue name via `MsmqSubscriptionStorageConfig` section.")] - public static Configure MsmqSubscriptionStorage(this Configure config, string endpointName) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Message = "Assign the queue name via `MsmqSubscriptionStorageConfig` section.")] - public static Address Queue { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/Entry.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/Entry.cs deleted file mode 100644 index bc9f547642d..00000000000 --- a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/Entry.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus.Persistence.Msmq.SubscriptionStorage -{ - using System; - using Unicast.Subscriptions; - - /// - /// Describes an entry in the list of subscriptions. - /// - [Serializable] - class Entry - { - /// - /// Gets the message type for the subscription entry. - /// - public MessageType MessageType { get; set; } - - /// - /// Gets the subscription request message. - /// - public Address Subscriber { get; set; } - } -} diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/IMsmqSubscriptionStorageQueue.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/IMsmqSubscriptionStorageQueue.cs new file mode 100644 index 00000000000..b3981e09d33 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/IMsmqSubscriptionStorageQueue.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + + interface IMsmqSubscriptionStorageQueue + { + IEnumerable GetAllMessages(); + string Send(string body, string label); + void TryReceiveById(string messageId); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionMessage.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionMessage.cs new file mode 100644 index 00000000000..28ae2ab6a80 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionMessage.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Messaging; + + class MsmqSubscriptionMessage + { + public MsmqSubscriptionMessage(Message m) + { + Body = m.Body; + Label = m.Label; + Id = m.Id; + ArrivedTime = m.ArrivedTime; + } + + public MsmqSubscriptionMessage() + { + } + + public DateTime ArrivedTime { get; set; } + + public object Body { get; set; } + + public string Label { get; set; } + + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionPersistence.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionPersistence.cs index 1c4db407beb..831ff837bef 100644 --- a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionPersistence.cs +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionPersistence.cs @@ -2,20 +2,21 @@ { using Config; using Logging; - using Persistence.SubscriptionStorage; + using Transport; /// - /// Provides subscription storage using a msmq queue as the backing store + /// Provides subscription storage using a msmq queue as the backing store. /// - public class MsmqSubscriptionPersistence:Feature + public class MsmqSubscriptionPersistence : Feature { internal MsmqSubscriptionPersistence() { } + /// - /// Invoked if the feature is activated + /// Invoked if the feature is activated. /// - /// The feature context + /// The feature context. protected internal override void Setup(FeatureConfigurationContext context) { var queueName = context.Settings.GetOrDefault("MsmqSubscriptionPersistence.QueueName"); @@ -27,7 +28,7 @@ protected internal override void Setup(FeatureConfigurationContext context) if (cfg == null) { Logger.Warn("Could not find configuration section for Msmq Subscription Storage and no name was specified for this endpoint. Going to default the subscription queue"); - queueName = "NServiceBus.Subscriptions"; + queueName = "NServiceBus.Subscriptions"; } else { @@ -35,14 +36,16 @@ protected internal override void Setup(FeatureConfigurationContext context) } } - var storageQueue = Address.Parse(queueName); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.StorageQueue, storageQueue); + if (queueName != null) + { + context.Settings.Get().BindSending(queueName); + } - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(s => s.Queue, storageQueue) - .ConfigureProperty(s => s.TransactionsEnabled, context.Settings.Get("Transactions.Enabled")); + context.Container.ConfigureComponent(b => + { + var queue = new MsmqSubscriptionStorageQueue(MsmqAddress.Parse(queueName)); + return new MsmqSubscriptionStorage(queue); + }, DependencyLifecycle.SingleInstance); } static ILog Logger = LogManager.GetLogger(typeof(MsmqSubscriptionPersistence)); diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorage.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorage.cs index 8a207ca9aca..16c33e46e41 100644 --- a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorage.cs +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorage.cs @@ -1,247 +1,229 @@ -namespace NServiceBus.Persistence.SubscriptionStorage +namespace NServiceBus { using System; using System.Collections.Generic; - using System.Messaging; - using System.Transactions; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Extensibility; using Logging; - using Msmq.SubscriptionStorage; using Unicast.Subscriptions.MessageDrivenSubscriptions; using MessageType = Unicast.Subscriptions.MessageType; - /// - /// Provides functionality for managing message subscriptions - /// using MSMQ. - /// - class MsmqSubscriptionStorage : ISubscriptionStorage, IDisposable + class MsmqSubscriptionStorage : IInitializableSubscriptionStorage, IDisposable { - public bool TransactionsEnabled { get; set; } + public MsmqSubscriptionStorage(IMsmqSubscriptionStorageQueue storageQueue) + { + this.storageQueue = storageQueue; + } - void ISubscriptionStorage.Init() + public void Dispose() { - var path = MsmqUtilities.GetFullPath(Queue); + // Filled in by Janitor.fody + } - q = new MessageQueue(path); + public void Init() + { + var messages = storageQueue.GetAllMessages() + .OrderByDescending(m => m.ArrivedTime) + .ThenBy(x => x.Id) // ensure same order of messages with same timestamp accross all endpoints + .ToArray(); - bool transactional; try { - transactional = q.Transactional; - } - catch (Exception ex) - { - throw new ArgumentException(string.Format("There is a problem with the subscription storage queue {0}. See enclosed exception for details.", Queue), ex); - } - - if (!transactional && TransactionsEnabled) - throw new ArgumentException("Queue must be transactional (" + Queue + ")."); + rwLock.EnterWriteLock(); - var messageReadPropertyFilter = new MessagePropertyFilter { Id = true, Body = true, Label = true }; - - q.Formatter = new XmlMessageFormatter(new[] { typeof(string) }); + foreach (var m in messages) + { + var messageTypeString = m.Body as string; + var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings + var subscriber = Deserialize(m.Label); - q.MessageReadPropertyFilter = messageReadPropertyFilter; + Dictionary endpointSubscriptions; + if (!lookup.TryGetValue(subscriber, out endpointSubscriptions)) + { + lookup[subscriber] = endpointSubscriptions = new Dictionary(); + } - foreach (var m in q.GetAllMessages()) + if (endpointSubscriptions.ContainsKey(messageType)) + { + // this message is stale and can be removed + storageQueue.TryReceiveById(m.Id); + } + else + { + endpointSubscriptions[messageType] = m.Id; + } + } + } + finally { - var subscriber = Address.Parse(m.Label); - var messageTypeString = m.Body as string; - var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings - - entries.Add(new Entry { MessageType = messageType, Subscriber = subscriber }); - AddToLookup(subscriber, messageType, m.Id); + rwLock.ExitWriteLock(); } } - IEnumerable
ISubscriptionStorage.GetSubscriberAddressesForMessage(IEnumerable messageTypes) - { - var result = new List
(); - - lock (locker) - foreach (var e in entries) - foreach (var m in messageTypes) - if (e.MessageType == m) - if (!result.Contains(e.Subscriber)) - result.Add(e.Subscriber); - - return result; - } - - /// - /// Checks if configuration is wrong - endpoint isn't transactional and - /// object isn't configured to handle own transactions. - /// - private bool ConfigurationIsWrong() + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) { - return (Transaction.Current == null && !DontUseExternalTransaction); - } + var messagelist = messageTypes.ToArray(); + var result = new HashSet(); - void ISubscriptionStorage.Subscribe(Address address, IEnumerable messageTypes) - { - lock (locker) + try { - foreach (var messageType in messageTypes) + // note: ReaderWriterLockSlim has a thread affinity and cannot be used with await! + rwLock.EnterReadLock(); + + foreach (var subscribers in lookup) { - var found = false; - foreach (var e in entries) - if (e.MessageType == messageType && e.Subscriber == address) + foreach (var messageType in messagelist) + { + string messageId; + if (subscribers.Value.TryGetValue(messageType, out messageId)) { - found = true; - break; + result.Add(subscribers.Key); } - - if (!found) - { - Add(address, messageType); - - entries.Add(new Entry { MessageType = messageType, Subscriber = address }); - - log.Debug("Subscriber " + address + " added for message " + messageType + "."); } } } - } - - void ISubscriptionStorage.Unsubscribe(Address address, IEnumerable messageTypes) - { - lock (locker) + finally { - foreach (var e in entries.ToArray()) - foreach (var messageType in messageTypes) - if (e.MessageType == messageType && e.Subscriber == address) - { - Remove(address, messageType); - - entries.Remove(e); - - log.Debug("Subscriber " + address + " removed for message " + messageType + "."); - } + rwLock.ExitReadLock(); } + + return Task.FromResult>(result); } - /// - /// Adds a message to the subscription store. - /// - public void Add(Address subscriber, MessageType messageType) + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { - var toSend = new Message { Formatter = q.Formatter, Recoverable = true, Label = subscriber.ToString(), Body = messageType.TypeName + ", Version=" + messageType.Version }; + var body = $"{messageType.TypeName}, Version={messageType.Version}"; + var label = Serialize(subscriber); + var messageId = storageQueue.Send(body, label); - q.Send(toSend, GetTransactionType()); + AddToLookup(subscriber, messageType, messageId); - AddToLookup(subscriber, messageType, toSend.Id); + log.DebugFormat($"Subscriber {subscriber.TransportAddress} added for message {messageType}."); + + return TaskEx.CompletedTask; } - /// - /// Removes a message from the subscription store. - /// - public void Remove(Address subscriber, MessageType messageType) + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { var messageId = RemoveFromLookup(subscriber, messageType); - if (messageId == null) - return; - - q.ReceiveById(messageId, GetTransactionType()); - } - - /// - /// Returns the transaction type (automatic or single) that should be used - /// based on the configuration of enlisting into external transactions. - /// - private MessageQueueTransactionType GetTransactionType() - { - if (!TransactionsEnabled) + if (messageId != null) { - return MessageQueueTransactionType.None; + storageQueue.TryReceiveById(messageId); } - if (ConfigurationIsWrong()) - { - throw new InvalidOperationException("This endpoint is not configured to be transactional. Processing subscriptions on a non-transactional endpoint is not supported by default. If you still wish to do so, please set the 'DontUseExternalTransaction' property of MsmqSubscriptionStorage to 'true'.\n\nThe recommended solution to this problem is to include '.IsTransaction(true)' after '.MsmqTransport()' in your fluent initialization code, or if you're using NServiceBus.Host.exe to have the class which implements IConfigureThisEndpoint to also inherit AsA_Server or AsA_Publisher."); - } + log.Debug($"Subscriber {subscriber.TransportAddress} removed for message {messageType}."); - var t = MessageQueueTransactionType.Automatic; - if (DontUseExternalTransaction) - { - t = MessageQueueTransactionType.Single; - } + return TaskEx.CompletedTask; + } - return t; + static string Serialize(Subscriber subscriber) + { + return $"{subscriber.TransportAddress}|{subscriber.Endpoint}"; } - /// - /// Gets/sets whether or not to use a transaction started outside the - /// subscription store. - /// - public virtual bool DontUseExternalTransaction { get; set; } - - /// - /// Sets the address of the queue where subscription messages will be stored. - /// For a local queue, just use its name - msmq specific info isn't needed. - /// - public Address Queue + static Subscriber Deserialize(string serializedForm) { - get; - set; + var parts = serializedForm.Split(EntrySeparator, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0 || parts.Length > 2) + { + log.Error($"Invalid format of subscription entry: {serializedForm}."); + return null; + } + var endpointName = parts.Length > 1 + ? parts[1] + : null; + + return new Subscriber(parts[0], endpointName); } - /// - /// Adds a message to the lookup to find message from - /// subscriber, to message type, to message id - /// - private void AddToLookup(Address subscriber, MessageType typeName, string messageId) + void AddToLookup(Subscriber subscriber, MessageType typeName, string messageId) { - lock (lookup) + try { + // note: ReaderWriterLockSlim has a thread affinity and cannot be used with await! + rwLock.EnterWriteLock(); + Dictionary dictionary; if (!lookup.TryGetValue(subscriber, out dictionary)) { - lookup[subscriber] = dictionary = new Dictionary(); + dictionary = new Dictionary(); } - - if (!dictionary.ContainsKey(typeName)) + else { - dictionary.Add(typeName, messageId); + // replace existing subscriber + lookup.Remove(subscriber); } + + dictionary[typeName] = messageId; + lookup[subscriber] = dictionary; + } + finally + { + rwLock.ExitWriteLock(); } } - string RemoveFromLookup(Address subscriber, MessageType typeName) + string RemoveFromLookup(Subscriber subscriber, MessageType typeName) { - string messageId = null; - lock (lookup) + try { - Dictionary endpoints; - if (lookup.TryGetValue(subscriber, out endpoints)) + // note: ReaderWriterLockSlim has a thread affinity and cannot be used with await! + rwLock.EnterWriteLock(); + + Dictionary subscriptions; + if (lookup.TryGetValue(subscriber, out subscriptions)) { - if (endpoints.TryGetValue(typeName, out messageId)) + string messageId; + if (subscriptions.TryGetValue(typeName, out messageId)) { - endpoints.Remove(typeName); - if (endpoints.Count == 0) + subscriptions.Remove(typeName); + if (subscriptions.Count == 0) { lookup.Remove(subscriber); } + + return messageId; } } } - return messageId; - } - - MessageQueue q; + finally + { + rwLock.ExitWriteLock(); + } - /// - /// lookup from subscriber, to message type, to message id - /// - readonly Dictionary> lookup = new Dictionary>(); + return null; + } - readonly List entries = new List(); - readonly object locker = new object(); + Dictionary> lookup = new Dictionary>(SubscriberComparer); + IMsmqSubscriptionStorageQueue storageQueue; + ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); static ILog log = LogManager.GetLogger(typeof(ISubscriptionStorage)); + static TransportAddressEqualityComparer SubscriberComparer = new TransportAddressEqualityComparer(); - public void Dispose() + static readonly char[] EntrySeparator = { - // Filled in by Janitor.fody + '|' + }; + + sealed class TransportAddressEqualityComparer : IEqualityComparer + { + public bool Equals(Subscriber x, Subscriber y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return string.Equals(x.TransportAddress, y.TransportAddress, StringComparison.OrdinalIgnoreCase); + } + + public int GetHashCode(Subscriber obj) + { + return (obj.TransportAddress != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(obj.TransportAddress) : 0); + } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Config/MsmqSubscriptionStorageConfig.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageConfig.cs similarity index 75% rename from src/NServiceBus.Core/Config/MsmqSubscriptionStorageConfig.cs rename to src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageConfig.cs index b231093ed41..9383c763e1f 100644 --- a/src/NServiceBus.Core/Config/MsmqSubscriptionStorageConfig.cs +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageConfig.cs @@ -14,14 +14,8 @@ public class MsmqSubscriptionStorageConfig : ConfigurationSection [ConfigurationProperty("Queue", IsRequired = true)] public string Queue { - get - { - return this["Queue"] as string; - } - set - { - this["Queue"] = value; - } + get { return this["Queue"] as string; } + set { this["Queue"] = value; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageQueue.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageQueue.cs new file mode 100644 index 00000000000..210c2dfafc1 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/MsmqSubscriptionStorageQueue.cs @@ -0,0 +1,64 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Messaging; + + class MsmqSubscriptionStorageQueue : IMsmqSubscriptionStorageQueue + { + public MsmqSubscriptionStorageQueue(MsmqAddress queueAddress) + { + q = new MessageQueue(queueAddress.FullPath); + + var messageReadPropertyFilter = new MessagePropertyFilter + { + Id = true, + Body = true, + Label = true, + ArrivedTime = true + }; + + q.Formatter = new XmlMessageFormatter(new[] + { + typeof(string) + }); + + q.MessageReadPropertyFilter = messageReadPropertyFilter; + } + + public IEnumerable GetAllMessages() + { + return q.GetAllMessages().Select(m => new MsmqSubscriptionMessage(m)); + } + + public string Send(string body, string label) + { + var toSend = new Message() + { + Recoverable = true, + Formatter = q.Formatter, + Body = body, + Label = label + }; + + q.Send(toSend, MessageQueueTransactionType.None); + + return toSend.Id; + } + + public void TryReceiveById(string messageId) + { + try + { + q.ReceiveById(messageId, MessageQueueTransactionType.None); + } + catch (InvalidOperationException) + { + // thrown when message not found + } + } + + MessageQueue q; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/SubscriptionsQueueCreator.cs b/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/SubscriptionsQueueCreator.cs deleted file mode 100644 index 076ad3ea4d2..00000000000 --- a/src/NServiceBus.Core/Persistence/Msmq/SubscriptionStorage/SubscriptionsQueueCreator.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Persistence.SubscriptionStorage -{ - using Unicast.Queuing; - - class SubscriptionsQueueCreator : IWantQueueCreated - { - public Address StorageQueue { get; set; } - - public Address Address - { - get { return StorageQueue; } - } - - public bool ShouldCreateQueue() - { - return StorageQueue != null; - } - } -} diff --git a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs index f79450a8553..ca30763ec6b 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceConfig.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceConfig.cs @@ -1,45 +1,49 @@ namespace NServiceBus { using System; - using NServiceBus.Persistence; + using Persistence; /// - /// Enables users to select persistence by calling .UsePersistence() + /// Enables users to select persistence by calling .UsePersistence(). /// public static class PersistenceConfig { /// - /// Configures the given persistence to be used + /// Configures the given persistence to be used. /// - /// The persistence definition eg , NHibernate etc - /// The configuration object since this is an extention method - public static PersistenceExtentions UsePersistence(this BusConfiguration config) where T : PersistenceDefinition + /// The persistence definition eg , NHibernate etc. + /// The instance to apply the settings to. + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) where T : PersistenceDefinition { - var type = typeof(PersistenceExtentions<>).MakeGenericType(typeof(T)); - return (PersistenceExtentions)Activator.CreateInstance(type, config.Settings); + Guard.AgainstNull(nameof(config), config); + var type = typeof(PersistenceExtensions<>).MakeGenericType(typeof(T)); + return (PersistenceExtensions) Activator.CreateInstance(type, config.Settings); } /// - /// Configures the given persistence to be used for a specific storage type + /// Configures the given persistence to be used for a specific storage type. /// - /// The persistence definition eg , NHibernate etc - /// The storage type - /// The configuration object since this is an extention method - public static PersistenceExtentions UsePersistence(this BusConfiguration config) where T : PersistenceDefinition - where S : StorageType + /// The persistence definition eg , NHibernate etc. + /// The storage type. + /// The instance to apply the settings to. + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config) where T : PersistenceDefinition + where S : StorageType { - var type = typeof(PersistenceExtentions<,>).MakeGenericType(typeof(T), typeof(S)); - return (PersistenceExtentions) Activator.CreateInstance(type, config.Settings); + Guard.AgainstNull(nameof(config), config); + var type = typeof(PersistenceExtensions<,>).MakeGenericType(typeof(T), typeof(S)); + return (PersistenceExtensions) Activator.CreateInstance(type, config.Settings); } /// - /// Configures the given persistence to be used + /// Configures the given persistence to be used. /// - /// The configuration object since this is an extention method - /// The persistence definition eg , NHibernate etc - public static PersistenceExtentions UsePersistence(this BusConfiguration config, Type definitionType) + /// The instance to apply the settings to. + /// The persistence definition eg , NHibernate etc. + public static PersistenceExtensions UsePersistence(this EndpointConfiguration config, Type definitionType) { - return new PersistenceExtentions(definitionType, config.Settings, null); + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(definitionType), definitionType); + return new PersistenceExtensions(definitionType, config.Settings, null); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs index 7a76887b0ce..0e2162e03cb 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceDefinition.cs @@ -3,65 +3,61 @@ using System; using System.Collections.Generic; using System.Linq; - using NServiceBus.Settings; + using Settings; /// - /// Base class for persistence definitions + /// Base class for persistence definitions. /// public abstract class PersistenceDefinition { /// - /// Used be the storage definitions to declare what they support + /// Used by the storage definitions to declare what they support. /// - /// protected void Supports(Action action) where T : StorageType { + Guard.AgainstNull(nameof(action), action); if (storageToActionMap.ContainsKey(typeof(T))) { - throw new Exception(string.Format("Action for {0} already defined.", typeof(T))); + throw new Exception($"Action for {typeof(T)} already defined."); } storageToActionMap[typeof(T)] = action; } /// - /// Used be the storage definitions to declare what they support + /// Used by the storage definitions to declare what they support. /// [ObsoleteEx( - RemoveInVersion = "7.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "Supports()")] + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Supports()")] protected void Supports(Storage storage, Action action) { - var storageType = StorageType.FromEnum(storage); - if (storageToActionMap.ContainsKey(storageType)) - { - throw new Exception(string.Format("Action for {0} already defined.", storage)); - } - storageToActionMap[storageType] = action; + throw new NotImplementedException(); } /// - /// Used be the storage definitions to declare what they support + /// Used by the storage definitions to declare what they support. /// protected void Defaults(Action action) { + Guard.AgainstNull(nameof(action), action); defaults.Add(action); } /// - /// True if supplied storage is supported + /// True if supplied storage is supported. /// [ObsoleteEx( RemoveInVersion = "7.0", TreatAsErrorFromVersion = "6.0", - Replacement = "HasSupportFor()")] + ReplacementTypeOrMember = "HasSupportFor()")] public bool HasSupportFor(Storage storage) { - return storageToActionMap.ContainsKey(StorageType.FromEnum(storage)); + throw new NotImplementedException(); } /// - /// True if supplied storage is supported + /// True if supplied storage is supported. /// public bool HasSupportFor() where T : StorageType { @@ -69,10 +65,11 @@ public bool HasSupportFor() where T : StorageType } /// - /// True if supplied storage is supported + /// True if supplied storage is supported. /// - public bool HasSupportFor(Type storageType) + public bool HasSupportFor(Type storageType) { + Guard.AgainstNull(nameof(storageType), storageType); return storageToActionMap.ContainsKey(storageType); } @@ -80,7 +77,7 @@ internal void ApplyActionForStorage(Type storageType, SettingsHolder settings) { if (!storageType.IsSubclassOf(typeof(StorageType))) { - throw new ArgumentException(string.Format("Storage type '{0}' is not a sub-class of StorageType", storageType.FullName), "storageType"); + throw new ArgumentException($"Storage type '{storageType.FullName}' is not a sub-class of StorageType", nameof(storageType)); } var actionForStorage = storageToActionMap[storageType]; actionForStorage(settings); diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs new file mode 100644 index 00000000000..118d3aab442 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/PersistenceExtensions.cs @@ -0,0 +1,130 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Configuration.AdvanceExtensibility; + using Persistence; + using Settings; + + /// + /// This class provides implementers of persisters with an extension mechanism for custom settings for specific storage + /// type via extension methods. + /// + /// The persister definition eg , , etc. + /// The storage type. + public class PersistenceExtensions : PersistenceExtensions + where T : PersistenceDefinition + where S : StorageType + { + /// + /// Initializes a new instance of . + /// + public PersistenceExtensions(SettingsHolder settings) : base(settings, typeof(S)) + { + } + } + + /// + /// This class provides implementers of persisters with an extension mechanism for custom settings via extension + /// methods. + /// + /// The persister definition eg , , etc. + public class PersistenceExtensions : PersistenceExtensions where T : PersistenceDefinition + { + /// + /// Default constructor. + /// + public PersistenceExtensions(SettingsHolder settings) : base(typeof(T), settings, null) + { + } + + /// + /// Constructor for a specific . + /// + protected PersistenceExtensions(SettingsHolder settings, Type storageType) : base(typeof(T), settings, storageType) + { + } + + /// + /// Defines the list of specific storage needs this persistence should provide. + /// + /// The list of storage needs. + [ObsoleteEx( + Message = "Example: config.UsePersistence().For(TimeoutStorage) should be changed to config.UsePersistence()", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "UsePersistence()")] + public new PersistenceExtensions For(params Storage[] specificStorages) + { + base.For(specificStorages); + return this; + } + } + + /// + /// This class provides implementers of persisters with an extension mechanism for custom settings via extension + /// methods. + /// + public class PersistenceExtensions : ExposeSettings + { + /// + /// Initializes a new instance of . + /// + public PersistenceExtensions(Type definitionType, SettingsHolder settings, Type storageType) + : base(settings) + { + List definitions; + if (!Settings.TryGet("PersistenceDefinitions", out definitions)) + { + definitions = new List(); + Settings.Set("PersistenceDefinitions", definitions); + } + + enabledPersistence = new EnabledPersistence + { + DefinitionType = definitionType, + SelectedStorages = new List() + }; + + + if (storageType != null) + { + var definition = definitionType.Construct(); + if (!definition.HasSupportFor(storageType)) + { + throw new Exception($"{definitionType.Name} does not support storage type {storageType.Name}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations."); + } + + enabledPersistence.SelectedStorages.Add(storageType); + } + + definitions.Add(enabledPersistence); + } + + + /// + /// Defines the list of specific storage needs this persistence should provide. + /// + /// The list of storage needs. + [ObsoleteEx( + Message = "Example: config.UsePersistence().For(TimeoutStorage) should be changed to config.UsePersistence()", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "UsePersistence() where T : PersistenceExtension where S : StorageType")] + public PersistenceExtensions For(params Storage[] specificStorages) + { + if (specificStorages == null || specificStorages.Length == 0) + { + throw new ArgumentException("Ensure at least one Storage is specified."); + } + + var list = specificStorages.Select(StorageType.FromEnum).ToArray(); + enabledPersistence.SelectedStorages.AddRange(list); + + return this; + } + + EnabledPersistence enabledPersistence; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceExtentions.cs b/src/NServiceBus.Core/Persistence/PersistenceExtentions.cs deleted file mode 100644 index 165ba164cf1..00000000000 --- a/src/NServiceBus.Core/Persistence/PersistenceExtentions.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Persistence; - using NServiceBus.Settings; - using Utils.Reflection; - - /// - /// This class provides implementers of persisters with an extension mechanism for custom settings for specific storage type via extention methods. - /// - /// The persister definition eg , , etc - /// The storage type - public class PersistenceExtentions : PersistenceExtentions - where T : PersistenceDefinition - where S : StorageType - { - /// - /// Default constructor. - /// - public PersistenceExtentions(SettingsHolder settings) : base(settings, typeof(S)) - { - } - } - - /// - /// This class provides implementers of persisters with an extension mechanism for custom settings via extention - /// methods. - /// - /// The persister definition eg , , etc - public class PersistenceExtentions : PersistenceExtentions where T : PersistenceDefinition - { - /// - /// Default constructor. - /// - public PersistenceExtentions(SettingsHolder settings) : base(typeof(T), settings, null) - { - } - - /// - /// Constructor for a specific - /// - protected PersistenceExtentions(SettingsHolder settings, Type storageType) : base(typeof(T), settings, storageType) - { - } - - /// - /// Defines the list of specific storage needs this persistence should provide - /// - /// The list of storage needs - [ObsoleteEx( - RemoveInVersion = "7.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "UsePersistence()")] - public new PersistenceExtentions For(params Storage[] specificStorages) - { - base.For(specificStorages); - return this; - } - } - - /// - /// This class provides implementers of persisters with an extension mechanism for custom settings via extention - /// methods. - /// - public class PersistenceExtentions : ExposeSettings - { - /// - /// Default constructor. - /// - public PersistenceExtentions(Type definitionType, SettingsHolder settings, Type storageType) - : base(settings) - { - List definitions; - if (!Settings.TryGet("PersistenceDefinitions", out definitions)) - { - definitions = new List(); - Settings.Set("PersistenceDefinitions", definitions); - } - - enabledPersistence = new EnabledPersistence - { - DefinitionType = definitionType, - SelectedStorages = new List(), - }; - - - if (storageType != null) - { - var definition = definitionType.Construct(); - if (!definition.HasSupportFor(storageType)) - { - throw new Exception(string.Format("{0} does not support storage type {1}. See http://docs.particular.net/nservicebus/persistence-in-nservicebus for supported variations.", definitionType.Name, storageType.Name)); - } - - enabledPersistence.SelectedStorages.Add(storageType); - } - - definitions.Add(enabledPersistence); - } - - - /// - /// Defines the list of specific storage needs this persistence should provide - /// - /// The list of storage needs - [ObsoleteEx( - RemoveInVersion = "7.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "UsePersistence()")] - public PersistenceExtentions For(params Storage[] specificStorages) - { - if (specificStorages == null || specificStorages.Length == 0) - { - throw new ArgumentException("Please make sure you specify at least one Storage."); - } - - var list = specificStorages.Select(StorageType.FromEnum).ToArray(); - enabledPersistence.SelectedStorages.AddRange(list); - - return this; - } - - EnabledPersistence enabledPersistence; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceStartup.cs b/src/NServiceBus.Core/Persistence/PersistenceStartup.cs index e20a88c3a5c..26747bf02ad 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceStartup.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceStartup.cs @@ -1,20 +1,15 @@ -namespace NServiceBus.Persistence +namespace NServiceBus { using System; using System.Collections.Generic; - using NServiceBus.Logging; - using NServiceBus.Settings; - using Utils.Reflection; + using Logging; + using Persistence; + using Settings; class PersistenceStartup : IWantToRunBeforeConfigurationIsFinalized { - const string errorMessage = "No persistence has been selected, please select your persistence by calling configuration.UsePersistence() in your class that implements either IConfigureThisEndpoint or INeedInitialization, where T can be any of the supported persistence option. If you were previously using RavenDB, note that it has been moved to its own stand alone nuget 'NServiceBus.RavenDB' and you'll need to install this package and then call configuration.UsePersistence()"; - - static ILog Logger = LogManager.GetLogger(typeof(PersistenceStartup)); - - public void Run(Configure config) + public void Run(SettingsHolder settings) { - var settings = config.Settings; List definitions; if (!settings.TryGet("PersistenceDefinitions", out definitions)) @@ -39,7 +34,7 @@ public void Run(Configure config) foreach (var storageType in definition.SelectedStorages) { - Logger.InfoFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", definition.DefinitionType.Name, storageType); + Logger.DebugFormat("Activating persistence '{0}' to provide storage for '{1}' storage.", definition.DefinitionType.Name, storageType); persistenceDefinition.ApplyActionForStorage(storageType, settings); resultingSupportedStorages.Add(storageType); } @@ -50,8 +45,14 @@ public void Run(Configure config) internal static bool HasSupportFor(ReadOnlySettings settings) where T : StorageType { - return settings.Get>("ResultingSupportedStorages") - .Contains(typeof(T)); + List supportedStorages; + settings.TryGet("ResultingSupportedStorages", out supportedStorages); + + return supportedStorages?.Contains(typeof(T)) ?? false; } + + const string errorMessage = "No persistence has been selected, select a persistence by calling endpointConfiguration.UsePersistence() in the class that implements either IConfigureThisEndpoint or INeedInitialization, where T can be any of the supported persistence option. If previously using RavenDB, note that it has been moved to its own stand alone nuget 'NServiceBus.RavenDB'. This package will need to be installed and then enabled by calling endpointConfiguration.UsePersistence()."; + + static ILog Logger = LogManager.GetLogger(typeof(PersistenceStartup)); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs b/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs index cad22d6703d..be72db37370 100644 --- a/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs +++ b/src/NServiceBus.Core/Persistence/PersistenceStorageMerger.cs @@ -1,10 +1,10 @@ -namespace NServiceBus.Persistence +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; - using NServiceBus.Settings; - using NServiceBus.Utils.Reflection; + using Persistence; + using Settings; class PersistenceStorageMerger { diff --git a/src/NServiceBus.Core/Persistence/Storage.cs b/src/NServiceBus.Core/Persistence/Storage.cs deleted file mode 100644 index 393b67a8ab6..00000000000 --- a/src/NServiceBus.Core/Persistence/Storage.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.Persistence -{ - /// - /// The storage needs of NServiceBus - /// - [ObsoleteEx( - RemoveInVersion = "7.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "NServiceBus.Persistence.StorageType")] - public enum Storage - { - /// - /// Storage for timeouts - /// - Timeouts = 1, - /// - /// Storage for subscriptions - /// - Subscriptions = 2, - /// - /// Storage for sagas - /// - Sagas = 3, - /// - /// Storage for gateway deduplication - /// - GatewayDeduplication = 4, - /// - /// Storage for the outbox - /// - Outbox = 5, - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/StorageType.cs b/src/NServiceBus.Core/Persistence/StorageType.cs index f5c7e5d6688..4eccc559497 100644 --- a/src/NServiceBus.Core/Persistence/StorageType.cs +++ b/src/NServiceBus.Core/Persistence/StorageType.cs @@ -5,88 +5,95 @@ using System.Linq; /// - /// The storage types used for NServiceBus needs + /// The storage types used for NServiceBus needs. /// public abstract class StorageType { - readonly Storage storage; - StorageType(Storage storage) { this.storage = storage; } - /// - /// Storage for timeouts - /// - public sealed class Timeouts : StorageType + /// + public override string ToString() { - internal Timeouts() : base(Storage.Timeouts) {} + return storage.ToString(); } - /// - /// Storage for subscriptions - /// - public sealed class Subscriptions : StorageType + internal static Type FromEnum(Storage storageEnum) { - internal Subscriptions() : base(Storage.Subscriptions) { } + switch (storageEnum) + { + case Storage.Timeouts: + return typeof(Timeouts); + case Storage.Subscriptions: + return typeof(Subscriptions); + case Storage.Sagas: + return typeof(Sagas); + case Storage.GatewayDeduplication: + return typeof(GatewayDeduplication); + case Storage.Outbox: + return typeof(Outbox); + default: + throw new ArgumentOutOfRangeException(nameof(storageEnum), "Unknown storage that has no equivalent StorageType"); + } } - /// - /// Storage for sagas - /// - public sealed class Sagas : StorageType + internal static List GetAvailableStorageTypes() { - internal Sagas() : base(Storage.Sagas) { } + return typeof(StorageType).GetNestedTypes().ToList(); } + Storage storage; + /// - /// Storage for gateway de-duplication + /// Storage for timeouts. /// - public sealed class GatewayDeduplication : StorageType + public sealed class Timeouts : StorageType { - internal GatewayDeduplication() : base(Storage.GatewayDeduplication) {} + internal Timeouts() : base(Storage.Timeouts) + { + } } /// - /// Storage for outbox + /// Storage for subscriptions. /// - public sealed class Outbox : StorageType + public sealed class Subscriptions : StorageType { - internal Outbox() : base(Storage.Outbox) { } + internal Subscriptions() : base(Storage.Subscriptions) + { + } } /// - /// + /// Storage for sagas. /// - /// - public override string ToString() + public sealed class Sagas : StorageType { - return storage.ToString(); + internal Sagas() : base(Storage.Sagas) + { + } } - internal static Type FromEnum(Storage storageEnum) + /// + /// Storage for gateway de-duplication. + /// + public sealed class GatewayDeduplication : StorageType { - switch (storageEnum) + internal GatewayDeduplication() : base(Storage.GatewayDeduplication) { - case Storage.Timeouts: - return typeof(Timeouts); - case Storage.Subscriptions: - return typeof(Subscriptions); - case Storage.Sagas: - return typeof(Sagas); - case Storage.GatewayDeduplication: - return typeof(GatewayDeduplication); - case Storage.Outbox: - return typeof(Outbox); - default: - throw new ArgumentOutOfRangeException("storageEnum", "Unknown storage that has no equivalent StorageType"); } } - internal static List GetAvailableStorageTypes() + /// + /// Storage for outbox. + /// + public sealed class Outbox : StorageType { - return typeof(StorageType).GetNestedTypes().ToList(); + internal Outbox() : base(Storage.Outbox) + { + } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Persistence/SynchronizedStorageSession.cs b/src/NServiceBus.Core/Persistence/SynchronizedStorageSession.cs new file mode 100644 index 00000000000..cbbba89b0e2 --- /dev/null +++ b/src/NServiceBus.Core/Persistence/SynchronizedStorageSession.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Persistence +{ + /// + /// Represents a storage session. + /// + public interface SynchronizedStorageSession + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Behavior.cs b/src/NServiceBus.Core/Pipeline/Behavior.cs new file mode 100644 index 00000000000..15e8d82f084 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Behavior.cs @@ -0,0 +1,31 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// This is the base interface to implement to create a that can be registered in a pipeline. + /// + /// The context that this should receive. + public abstract class Behavior : IBehavior where TContext : IBehaviorContext + { + /// + /// Called when the behavior is executed. + /// + /// The current context. + /// The next in the chain to execute. + public Task Invoke(TContext context, Func next) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(next), next); + return Invoke(context, () => next(context)); + } + + /// + /// Called when the behavior is executed. + /// + /// The current context. + /// The next in the chain to execute. + public abstract Task Invoke(TContext context, Func next); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/BehaviorChain.cs b/src/NServiceBus.Core/Pipeline/BehaviorChain.cs deleted file mode 100644 index da624c4cd70..00000000000 --- a/src/NServiceBus.Core/Pipeline/BehaviorChain.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using NServiceBus.Pipeline; - - class BehaviorChain where T : BehaviorContext - { - public BehaviorChain(IEnumerable behaviorList, T context, PipelineExecutor pipelineExecutor, BusNotifications notifications) - { - context.SetChain(this); - this.context = context; - this.notifications = notifications; - foreach (var behaviorType in behaviorList) - { - itemDescriptors.Enqueue(behaviorType); - } - - lookupSteps = pipelineExecutor.Incoming.Concat(pipelineExecutor.Outgoing).ToDictionary(rs => rs.BehaviorType); - } - - public void Invoke() - { - var outerPipe = false; - - try - { - if (!context.TryGet("Diagnostics.Pipe", out steps)) - { - outerPipe = true; - steps = new Observable(); - context.Set("Diagnostics.Pipe", steps); - notifications.Pipeline.InvokeReceiveStarted(steps); - } - - InvokeNext(context); - - if (outerPipe) - { - steps.OnCompleted(); - } - } - catch (Exception ex) - { - if (outerPipe) - { - steps.OnError(ex); - } - - throw; - } - finally - { - if (outerPipe) - { - context.Remove("Diagnostics.Pipe"); - } - } - } - - public void TakeSnapshot() - { - snapshots.Push(new Queue(itemDescriptors)); - } - - public void DeleteSnapshot() - { - itemDescriptors = new Queue(snapshots.Pop()); - } - - void InvokeNext(T context) - { - if (itemDescriptors.Count == 0) - { - return; - } - - var behaviorType = itemDescriptors.Dequeue(); - var stepEnded = new Observable(); - - try - { - steps.OnNext(new StepStarted(lookupSteps[behaviorType].StepId, behaviorType, stepEnded)); - - var instance = (IBehavior) context.Builder.Build(behaviorType); - - var duration = Stopwatch.StartNew(); - - instance.Invoke(context, () => - { - duration.Stop(); - InvokeNext(context); - duration.Start(); - }); - - duration.Stop(); - - stepEnded.OnNext(new StepEnded(duration.Elapsed)); - stepEnded.OnCompleted(); - } - catch (Exception ex) - { - stepEnded.OnError(ex); - - throw; - } - } - - readonly BusNotifications notifications; - T context; - Queue itemDescriptors = new Queue(); - Dictionary lookupSteps; - Stack> snapshots = new Stack>(); - Observable steps; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/BehaviorContext.cs b/src/NServiceBus.Core/Pipeline/BehaviorContext.cs index cebafafcb5d..f0fc6366dd3 100644 --- a/src/NServiceBus.Core/Pipeline/BehaviorContext.cs +++ b/src/NServiceBus.Core/Pipeline/BehaviorContext.cs @@ -1,158 +1,17 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { - using System; - using System.Collections.Generic; + using Extensibility; using ObjectBuilder; + using Pipeline; - /// - /// Base class for a pipeline behavior. - /// - public abstract class BehaviorContext + abstract class BehaviorContext : ContextBag, IBehaviorContext { - /// - /// Create an instance of . - /// - /// The parent context - protected BehaviorContext(BehaviorContext parentContext) + protected BehaviorContext(IBehaviorContext parentContext) : base(parentContext?.Extensions) { - this.parentContext = parentContext; } - /// - /// The current - /// - public IBuilder Builder - { - get { return Get(); } - } - - internal void SetChain(dynamic chain) - { - this.chain = chain; - } - - internal IDisposable CreateSnapshotRegion() - { - return new SnapshotRegion(chain); - } - - /// - /// Retrieves the specified type from the context. - /// - /// The type to retrieve. - /// The type instance. - public T Get() - { - return Get(typeof(T).FullName); - } - - /// - /// Tries to retrieves the specified type from the context. - /// - /// The type to retrieve. - /// The type instance. - /// true if found, otherwise false. - public bool TryGet(out T result) - { - return TryGet(typeof(T).FullName, out result); - } - - /// - /// Tries to retrieves the specified type from the context using a custom key. - /// - /// The type to retrieve. - /// The custom key. - /// The type instance. - /// true if found, otherwise false. - public bool TryGet(string key, out T result) - { - object value; - if (stash.TryGetValue(key, out value)) - { - result = (T) value; - return true; - } - - if (parentContext != null) - { - return parentContext.TryGet(key, out result); - } - - if (typeof(T).IsValueType) - { - result = default(T); - return false; - } - - result = default(T); - return false; - } - - /// - /// Retrieves the specified type from the context. - /// - /// The type to retrieve. - /// The custom key. - /// The type instance. - public T Get(string key) - { - T result; - - if (!TryGet(key, out result)) - { - throw new KeyNotFoundException("No item found in behavior context with key: " + key); - } - - return result; - } - - /// - /// Stores the type instance in the context. - /// - /// The type to store. - /// The instance type to store. - public void Set(T t) - { - Set(typeof(T).FullName, t); - } - - /// - /// Stores the type instance in the context using a custom key. - /// - /// The type to store. - /// The custom key. - /// The instance type to store. - public void Set(string key, T t) - { - stash[key] = t; - } - - /// - /// Removes the instance type from the context. - /// - /// The type to remove. - public void Remove() - { - Remove(typeof(T).FullName); - } - - /// - /// Removes a entry from the context using the specifed custom key. - /// - /// The custom key. - public void Remove(string key) - { - stash.Remove(key); - } - - /// - /// Access to the parent context - /// - protected readonly BehaviorContext parentContext; - dynamic chain; - - internal bool handleCurrentMessageLaterWasCalled; + public IBuilder Builder => Get(); - Dictionary stash = new Dictionary(); + public ContextBag Extensions => this; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/BehaviorContextStacker.cs b/src/NServiceBus.Core/Pipeline/BehaviorContextStacker.cs deleted file mode 100644 index 988a2ce131e..00000000000 --- a/src/NServiceBus.Core/Pipeline/BehaviorContextStacker.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - using System.Collections.Generic; - using System.Threading; - - class BehaviorContextStacker : IDisposable - { - public BehaviorContext Current - { - get - { - if (behaviorContextStack.Value.Count == 0) - { - return null; - } - - return behaviorContextStack.Value.Peek(); - } - } - - public void Dispose() - { - //Injected - } - - public void Push(BehaviorContext item) - { - behaviorContextStack.Value.Push(item); - } - - public void Pop() - { - behaviorContextStack.Value.Pop(); - } - - //until we get the internal container going we - ThreadLocal> behaviorContextStack = new ThreadLocal>(() => new Stack()); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/BehaviorTypeChecker.cs b/src/NServiceBus.Core/Pipeline/BehaviorTypeChecker.cs index 5d8fae00ec6..b36403e392d 100644 --- a/src/NServiceBus.Core/Pipeline/BehaviorTypeChecker.cs +++ b/src/NServiceBus.Core/Pipeline/BehaviorTypeChecker.cs @@ -1,48 +1,45 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { using System; - using System.Linq; + using System.Collections.Generic; + using Pipeline; static class BehaviorTypeChecker { public static void ThrowIfInvalid(Type behavior, string paramName) { - if (behavior == null) - { - throw new ArgumentNullException(paramName); - } + Guard.AgainstNull(nameof(behavior), behavior); if (behavior.IsAbstract) { - throw new ArgumentException(string.Format("The behavior '{0}' is invalid since it is abstract.", behavior.Name), paramName); + throw new ArgumentException($"The behavior '{behavior.Name}' is invalid since it is abstract.", paramName); } if (behavior.IsGenericTypeDefinition) { - throw new ArgumentException(string.Format("The behavior '{0}' is invalid since it is an open generic.", behavior.Name), paramName); + throw new ArgumentException($"The behavior '{behavior.Name}' is invalid since it is an open generic.", paramName); } - if (!IsAssignableToIBehavior(behavior)) + if (!behavior.IsBehavior()) { - throw new ArgumentException(string.Format("The behavior '{0}' is invalid since it does not implement IBehavior.", behavior.Name), paramName); + throw new ArgumentException($@"The behavior '{behavior.Name}' is invalid since it does not implement IBehavior.", paramName); } - } - - static Type iBehaviorType = typeof(IBehavior<>); - - static bool IsAssignableToIBehavior(Type givenType) - { - var interfaceTypes = givenType.GetInterfaces(); - if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == iBehaviorType)) + var inputContextType = behavior.GetInputContext(); + if (NotAllowedInterfaces.Contains(inputContextType)) { - return true; + throw new ArgumentException($@"The behavior '{behavior.Name}' is invalid since the TFrom {inputContextType} context of IBehavior is not intended to be used.", paramName); } - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == iBehaviorType) - return true; - - var baseType = givenType.BaseType; - if (baseType == null) return false; - - return IsAssignableToIBehavior(baseType); + var outputContextType = behavior.GetOutputContext(); + if (NotAllowedInterfaces.Contains(outputContextType)) + { + throw new ArgumentException($@"The behavior '{behavior.Name}' is invalid since the TTo {outputContextType} context of IBehavior is not intended to be used.", paramName); + } } + + static HashSet NotAllowedInterfaces = new HashSet + { + typeof(IBehaviorContext), + typeof(IIncomingContext), + typeof(IOutgoingContext) + }; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/ConnectorContextExtensions.cs b/src/NServiceBus.Core/Pipeline/ConnectorContextExtensions.cs new file mode 100644 index 00000000000..c1b02f084a3 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/ConnectorContextExtensions.cs @@ -0,0 +1,247 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Persistence; + using Pipeline; + using Routing; + using Transport; + + /// + /// Contains extensions methods to map behavior contexts. + /// + public static class ConnectorContextExtensions + { + /// + /// Creates a based on the current context. + /// + public static IRoutingContext CreateRoutingContext(this ForkConnector forkConnector, OutgoingMessage outgoingMessage, string localAddress, ITransportReceiveContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNullAndEmpty(nameof(localAddress), localAddress); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new RoutingContext(outgoingMessage, new UnicastRoutingStrategy(localAddress), sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IRoutingContext CreateRoutingContext(this StageConnector stageConnector, OutgoingMessage outgoingMessage, RoutingStrategy routingStrategy, IForwardingContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategy), routingStrategy); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new RoutingContext(outgoingMessage, routingStrategy, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IRoutingContext CreateRoutingContext(this StageConnector stageConnector, OutgoingMessage outgoingMessage, RoutingStrategy routingStrategy, IAuditContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategy), routingStrategy); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new RoutingContext(outgoingMessage, routingStrategy, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IRoutingContext CreateRoutingContext(this StageConnector stageConnector, OutgoingMessage outgoingMessage, IReadOnlyCollection routingStrategies, IOutgoingPhysicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategies), routingStrategies); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new RoutingContext(outgoingMessage, routingStrategies, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IIncomingPhysicalMessageContext CreateIncomingPhysicalMessageContext(this StageForkConnector stageForkConnector, IncomingMessage incomingMessage, ITransportReceiveContext sourceContext) + { + Guard.AgainstNull(nameof(incomingMessage), incomingMessage); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new IncomingPhysicalMessageContext(incomingMessage, sourceContext); + } + + internal static IIncomingPhysicalMessageContext CreateIncomingPhysicalMessageContext(this IStageForkConnector stageForkConnector, IncomingMessage incomingMessage, ITransportReceiveContext sourceContext) + { + return new IncomingPhysicalMessageContext(incomingMessage, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IIncomingPhysicalMessageContext CreateIncomingPhysicalMessageContext(this StageConnector stageConnector, IncomingMessage incomingMessage, ITransportReceiveContext sourceContext) + { + Guard.AgainstNull(nameof(incomingMessage), incomingMessage); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new IncomingPhysicalMessageContext(incomingMessage, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IIncomingLogicalMessageContext CreateIncomingLogicalMessageContext(this StageConnector stageConnector, LogicalMessage logicalMessage, IIncomingPhysicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(logicalMessage), logicalMessage); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new IncomingLogicalMessageContext(logicalMessage, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IInvokeHandlerContext CreateInvokeHandlerContext(this StageConnector stageConnector, MessageHandler messageHandler, CompletableSynchronizedStorageSession storageSession, IIncomingLogicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(messageHandler), messageHandler); + Guard.AgainstNull(nameof(storageSession), storageSession); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new InvokeHandlerContext(messageHandler, storageSession, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IBatchDispatchContext CreateBatchDispatchContext(this StageForkConnector stageForkConnector, IReadOnlyCollection transportOperations, IIncomingPhysicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(transportOperations), transportOperations); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new BatchDispatchContext(transportOperations, sourceContext); + } + + internal static IBatchDispatchContext CreateBatchDispatchContext(this IStageForkConnector stageForkConnector, IReadOnlyCollection transportOperations, IIncomingPhysicalMessageContext sourceContext) + { + return new BatchDispatchContext(transportOperations, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IDispatchContext CreateDispatchContext(this StageConnector stageConnector, IReadOnlyCollection transportOperations, IBatchDispatchContext sourceContext) + { + Guard.AgainstNull(nameof(transportOperations), transportOperations); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new DispatchContext(transportOperations, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IDispatchContext CreateDispatchContext(this StageConnector stageConnector, IReadOnlyCollection transportOperations, IRoutingContext sourceContext) + { + Guard.AgainstNull(nameof(transportOperations), transportOperations); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new DispatchContext(transportOperations, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this StageConnector stageConnector, OutgoingLogicalMessage outgoingMessage, IReadOnlyCollection routingStrategies, IOutgoingPublishContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategies), routingStrategies); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new OutgoingLogicalMessageContext( + sourceContext.MessageId, + sourceContext.Headers, + outgoingMessage, + routingStrategies, + sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this StageConnector stageConnector, OutgoingLogicalMessage outgoingMessage, IReadOnlyCollection routingStrategies, IOutgoingReplyContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategies), routingStrategies); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new OutgoingLogicalMessageContext( + sourceContext.MessageId, + sourceContext.Headers, + outgoingMessage, + routingStrategies, + sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IOutgoingLogicalMessageContext CreateOutgoingLogicalMessageContext(this StageConnector stageConnector, OutgoingLogicalMessage outgoingMessage, IReadOnlyCollection routingStrategies, IOutgoingSendContext sourceContext) + { + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + Guard.AgainstNull(nameof(routingStrategies), routingStrategies); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new OutgoingLogicalMessageContext( + sourceContext.MessageId, + sourceContext.Headers, + outgoingMessage, + routingStrategies, + sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IOutgoingPhysicalMessageContext CreateOutgoingPhysicalMessageContext(this StageConnector stageConnector, byte[] messageBody, IReadOnlyCollection routingStrategies, IOutgoingLogicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(messageBody), messageBody); + Guard.AgainstNull(nameof(routingStrategies), routingStrategies); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + return new OutgoingPhysicalMessageContext(messageBody, routingStrategies, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IAuditContext CreateAuditContext(this ForkConnector forkConnector, OutgoingMessage message, string auditAddress, IIncomingPhysicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + var connector = (IForkConnector) forkConnector; + return connector.CreateAuditContext(message, auditAddress, sourceContext); + } + + internal static IAuditContext CreateAuditContext(this IForkConnector forkConnector, OutgoingMessage message, string auditAddress, IIncomingPhysicalMessageContext sourceContext) + { + return new AuditContext(message, auditAddress, sourceContext); + } + + /// + /// Creates a based on the current context. + /// + public static IForwardingContext CreateForwardingContext(this ForkConnector forwardingContext, OutgoingMessage message, string forwardingAddress, IIncomingPhysicalMessageContext sourceContext) + { + Guard.AgainstNull(nameof(message), message); + Guard.AgainstNullAndEmpty(nameof(forwardingAddress), forwardingAddress); + Guard.AgainstNull(nameof(sourceContext), sourceContext); + + var connector = (IForkConnector) forwardingContext; + return connector.CreateForwardingContext(message, forwardingAddress, sourceContext); + } + + internal static IForwardingContext CreateForwardingContext(this IForkConnector forwardingContext, OutgoingMessage message, string forwardingAddress, IIncomingPhysicalMessageContext sourceContext) + { + return new ForwardingContext(message, forwardingAddress, sourceContext); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Contexts/IncomingContext.cs b/src/NServiceBus.Core/Pipeline/Contexts/IncomingContext.cs deleted file mode 100644 index 4c5be4a9cc1..00000000000 --- a/src/NServiceBus.Core/Pipeline/Contexts/IncomingContext.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace NServiceBus.Pipeline.Contexts -{ - using System.Collections.Generic; - using Unicast.Behaviors; - using Unicast.Messages; - - /// - /// Incoming pipeline context. - /// - public class IncomingContext : BehaviorContext - { - /// - /// Creates a new instance of . - /// - /// The parent context. - /// The incoming . - public IncomingContext(BehaviorContext parentContext, TransportMessage transportMessage) - : base(parentContext) - { - handleCurrentMessageLaterWasCalled = false; - - Set(IncomingPhysicalMessageKey, transportMessage); - - LogicalMessages = new List(); - } - - /// - /// true if DoNotInvokeAnyMoreHandlers has been called. - /// - public bool HandlerInvocationAborted { get; private set; } - - /// - /// Call this to stop the invocation of handlers. - /// - public void DoNotInvokeAnyMoreHandlers() - { - HandlerInvocationAborted = true; - } - - /// - /// The received message. - /// - public TransportMessage PhysicalMessage - { - get { return Get(IncomingPhysicalMessageKey); } - } - - /// - /// The received logical messages. - /// - public List LogicalMessages - { - get { return Get>(); } - set { Set(value); } - } - - /// - /// The current logical message being processed. - /// - public LogicalMessage IncomingLogicalMessage - { - get { return Get(IncomingLogicalMessageKey); } - set { Set(IncomingLogicalMessageKey, value); } - } - - /// - /// The current being executed. - /// - public MessageHandler MessageHandler - { - get { return Get(); } - set { Set(value); } - } - - internal const string IncomingPhysicalMessageKey = "NServiceBus.IncomingPhysicalMessage"; - const string IncomingLogicalMessageKey = "NServiceBus.IncomingLogicalMessageKey"; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Contexts/OutgoingContext.cs b/src/NServiceBus.Core/Pipeline/Contexts/OutgoingContext.cs deleted file mode 100644 index bce927a1316..00000000000 --- a/src/NServiceBus.Core/Pipeline/Contexts/OutgoingContext.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NServiceBus.Pipeline.Contexts -{ - using Unicast; - using Unicast.Messages; - - /// - /// Outgoing pipeline context. - /// - public class OutgoingContext : BehaviorContext - { - /// - /// Creates a new instance of . - /// - /// The parent context. - /// The delivery options. - /// The actual message to be sent out. - public OutgoingContext(BehaviorContext parentContext, DeliveryOptions deliveryOptions, LogicalMessage message) - : base(parentContext) - { - Set(deliveryOptions); - Set(OutgoingLogicalMessageKey, message); - } - - /// - /// Sending options. - /// - public DeliveryOptions DeliveryOptions - { - get { return Get(); } - } - - /// - /// Outgoing logical message. - /// - public LogicalMessage OutgoingLogicalMessage - { - get { return Get(OutgoingLogicalMessageKey); } - } - - /// - /// The received message, if any. - /// - public TransportMessage IncomingMessage - { - get - { - TransportMessage message; - - parentContext.TryGet(IncomingContext.IncomingPhysicalMessageKey, out message); - - return message; - } - } - - /// - /// The message about to be sent out. - /// - public TransportMessage OutgoingMessage - { - get { return Get(); } - } - - const string OutgoingLogicalMessageKey = "NServiceBus.OutgoingLogicalMessage"; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Contexts/RootContext.cs b/src/NServiceBus.Core/Pipeline/Contexts/RootContext.cs deleted file mode 100644 index 270418879c9..00000000000 --- a/src/NServiceBus.Core/Pipeline/Contexts/RootContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NServiceBus.Pipeline.Contexts -{ - using ObjectBuilder; - - class RootContext : BehaviorContext - { - public RootContext(IBuilder builder) : base(null) - { - Set(builder); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Dependency.cs b/src/NServiceBus.Core/Pipeline/Dependency.cs index a88b8304b25..a287cfd5833 100644 --- a/src/NServiceBus.Core/Pipeline/Dependency.cs +++ b/src/NServiceBus.Core/Pipeline/Dependency.cs @@ -1,14 +1,25 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { class Dependency { - public string Id { get; private set; } - public bool Enforce { get; private set; } + public enum DependencyDirection + { + Before = 1, + After = 2 + } - public Dependency(string id, bool enforce) + public Dependency(string dependantId, string dependsOnId, DependencyDirection direction, bool enforce) { - Id = id; + DependantId = dependantId; + DependsOnId = dependsOnId; + Direction = direction; Enforce = enforce; } + + public string DependantId { get; private set; } + public string DependsOnId { get; private set; } + public bool Enforce { get; private set; } + + public DependencyDirection Direction { get; private set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/ForkConnector.cs b/src/NServiceBus.Core/Pipeline/ForkConnector.cs new file mode 100644 index 00000000000..6131cce589e --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/ForkConnector.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// Forks into another independent pipeline. + /// + /// The context to connect from. + /// The context to fork an independent pipeline to. + public abstract class ForkConnector : Behavior, IForkConnector + where TFromContext : IBehaviorContext + where TForkContext : IBehaviorContext + { + /// + public abstract Task Invoke(TFromContext context, Func next, Func fork); + + /// + public sealed override Task Invoke(TFromContext context, Func next) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(next), next); + + return Invoke(context, next, ctx => + { + var cache = ctx.Extensions.Get(); + var pipeline = cache.Pipeline(); + return pipeline.Invoke(ctx); + }); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/ForkExtensions.cs b/src/NServiceBus.Core/Pipeline/ForkExtensions.cs new file mode 100644 index 00000000000..605aadd0be1 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/ForkExtensions.cs @@ -0,0 +1,18 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + + static class ForkExtensions + { + public static Task Fork(this IForkConnector forkConnector, TForkContext context) + where TForkContext : IBehaviorContext + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + return pipeline.Invoke(context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IBehavior.cs b/src/NServiceBus.Core/Pipeline/IBehavior.cs index 17bd3073e19..ad8bc8dd415 100644 --- a/src/NServiceBus.Core/Pipeline/IBehavior.cs +++ b/src/NServiceBus.Core/Pipeline/IBehavior.cs @@ -1,18 +1,29 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus.Pipeline { using System; + using System.Threading.Tasks; /// /// This is the base interface to implement to create a behavior that can be registered in a pipeline. /// - /// The context that this behavior should receive. - public interface IBehavior where TContext : BehaviorContext + /// The type of context that this behavior should receive. + /// The type of context that this behavior should output. + public interface IBehavior : IBehavior + where TInContext : IBehaviorContext + where TOutContext : IBehaviorContext { /// /// Called when the behavior is executed. /// /// The current context. - /// The next in the chain to execute. - void Invoke(TContext context, Action next); + /// The next in the chain to execute. + Task Invoke(TInContext context, Func next); + } + + /// + /// Base interface for all behaviors. + /// + public interface IBehavior + { } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IBehaviorContext.cs b/src/NServiceBus.Core/Pipeline/IBehaviorContext.cs new file mode 100644 index 00000000000..81c76bd932e --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IBehaviorContext.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Pipeline +{ + using Extensibility; + using ObjectBuilder; + + /// + /// Base interface for a pipeline behavior. + /// + public interface IBehaviorContext : IExtendable + { + /// + /// The current . + /// + IBuilder Builder { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IForkConnector.cs b/src/NServiceBus.Core/Pipeline/IForkConnector.cs new file mode 100644 index 00000000000..9ec311dc678 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IForkConnector.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using Pipeline; + + interface IForkConnector + { + } + + interface IForkConnector : IBehavior, IForkConnector + where TForkContext : IBehaviorContext + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IPipeline.cs b/src/NServiceBus.Core/Pipeline/IPipeline.cs new file mode 100644 index 00000000000..1fc2c098bac --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IPipeline.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + + interface IPipeline + { + } + + interface IPipeline : IPipeline + where TContext : IBehaviorContext + { + Task Invoke(TContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IPipelineCache.cs b/src/NServiceBus.Core/Pipeline/IPipelineCache.cs new file mode 100644 index 00000000000..b008249d2d5 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IPipelineCache.cs @@ -0,0 +1,10 @@ +namespace NServiceBus +{ + using Pipeline; + + interface IPipelineCache + { + IPipeline Pipeline() + where TContext : IBehaviorContext; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/IPipelineExecutor.cs new file mode 100644 index 00000000000..dbeb107fadd --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IPipelineExecutor.cs @@ -0,0 +1,10 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Transport; + + interface IPipelineExecutor + { + Task Invoke(MessageContext messageContext); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IPipelineTerminator.cs b/src/NServiceBus.Core/Pipeline/IPipelineTerminator.cs new file mode 100644 index 00000000000..52258b5a0f2 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IPipelineTerminator.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + /// + /// Marker interface for pipeline terminators. + /// + interface IPipelineTerminator + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/IStageConnector.cs b/src/NServiceBus.Core/Pipeline/IStageConnector.cs new file mode 100644 index 00000000000..689d56ecdde --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/IStageConnector.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using Pipeline; + + interface IStageConnector + { + } + + interface IStageConnector : IBehavior, IStageConnector + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + { + } + + interface IStageForkConnector : IForkConnector, IStageConnector + where TForkContext : IBehaviorContext + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/DeserializeLogicalMessagesConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/DeserializeLogicalMessagesConnector.cs new file mode 100644 index 00000000000..7c27cf2967b --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/DeserializeLogicalMessagesConnector.cs @@ -0,0 +1,149 @@ +// ReSharper disable ReturnTypeCanBeEnumerable.Local +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using Logging; + using Pipeline; + using Transport; + using Unicast.Messages; + + class DeserializeLogicalMessagesConnector : StageConnector + { + public DeserializeLogicalMessagesConnector(MessageDeserializerResolver deserializerResolver, LogicalMessageFactory logicalMessageFactory, MessageMetadataRegistry messageMetadataRegistry) + { + this.deserializerResolver = deserializerResolver; + this.logicalMessageFactory = logicalMessageFactory; + this.messageMetadataRegistry = messageMetadataRegistry; + } + + public override async Task Invoke(IIncomingPhysicalMessageContext context, Func stage) + { + var incomingMessage = context.Message; + + var messages = ExtractWithExceptionHandling(incomingMessage); + + foreach (var message in messages) + { + await stage(this.CreateIncomingLogicalMessageContext(message, context)).ConfigureAwait(false); + } + } + + static bool IsControlMessage(IncomingMessage incomingMessage) + { + return incomingMessage.Headers.ContainsKey(Headers.ControlMessageHeader) && incomingMessage.Headers[Headers.ControlMessageHeader] == Boolean.TrueString; + } + + LogicalMessage[] ExtractWithExceptionHandling(IncomingMessage message) + { + try + { + return Extract(message); + } + catch (Exception exception) + { + throw new MessageDeserializationException(message.MessageId, exception); + } + } + + LogicalMessage[] Extract(IncomingMessage physicalMessage) + { + // We need this check to be compatible with v3.3 endpoints, v3.3 control messages also include a body + if (IsControlMessage(physicalMessage)) + { + log.Debug("Received a control message. Skipping deserialization as control message data is contained in the header."); + return NoMessagesFound; + } + + if (physicalMessage.Body == null || physicalMessage.Body.Length == 0) + { + log.Debug("Received a message without body. Skipping deserialization."); + return NoMessagesFound; + } + + string messageTypeIdentifier; + var messageMetadata = new List(); + + if (physicalMessage.Headers.TryGetValue(Headers.EnclosedMessageTypes, out messageTypeIdentifier)) + { + foreach (var messageTypeString in messageTypeIdentifier.Split(EnclosedMessageTypeSeparator)) + { + var typeString = messageTypeString; + + if (DoesTypeHaveImplAddedByVersion3(typeString)) + { + continue; + } + + MessageMetadata metadata; + + if (IsV4OrBelowScheduledTask(typeString)) + { + metadata = messageMetadataRegistry.GetMessageMetadata(typeof(ScheduledTask)); + } + else + { + metadata = messageMetadataRegistry.GetMessageMetadata(typeString); + } + + if (metadata == null) + { + continue; + } + + messageMetadata.Add(metadata); + } + + if (messageMetadata.Count == 0 && physicalMessage.GetMesssageIntent() != MessageIntentEnum.Publish) + { + log.WarnFormat("Could not determine message type from message header '{0}'. MessageId: {1}", messageTypeIdentifier, physicalMessage.MessageId); + } + } + + var messageTypes = messageMetadata.Select(metadata => metadata.MessageType).ToList(); + var messageSerializer = deserializerResolver.Resolve(physicalMessage.Headers); + + // For nested behaviors who have an expectation ContentType existing + // add the default content type + physicalMessage.Headers[Headers.ContentType] = messageSerializer.ContentType; + + using (var stream = new MemoryStream(physicalMessage.Body)) + { + var deserializedMessages = messageSerializer.Deserialize(stream, messageTypes); + var logicalMessages = new LogicalMessage[deserializedMessages.Length]; + for (var i = 0; i < deserializedMessages.Length; i++) + { + var x = deserializedMessages[i]; + logicalMessages[i] = logicalMessageFactory.Create(x.GetType(), x); + } + return logicalMessages; + } + } + + static bool DoesTypeHaveImplAddedByVersion3(string existingTypeString) + { + return existingTypeString.Contains("__impl"); + } + + static bool IsV4OrBelowScheduledTask(string existingTypeString) + { + return existingTypeString.StartsWith("NServiceBus.Scheduling.Messages.ScheduledTask, NServiceBus.Core"); + } + + MessageDeserializerResolver deserializerResolver; + LogicalMessageFactory logicalMessageFactory; + MessageMetadataRegistry messageMetadataRegistry; + + static LogicalMessage[] NoMessagesFound = new LogicalMessage[0]; + + static char[] EnclosedMessageTypeSeparator = + { + ';' + }; + + static ILog log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingContext.cs new file mode 100644 index 00000000000..58463cc098d --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingContext.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Pipeline +{ + /// + /// The base interface for everything after the transport receive phase. + /// + public interface IIncomingContext : IBehaviorContext, IMessageProcessingContext + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingLogicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingLogicalMessageContext.cs new file mode 100644 index 00000000000..b025a6efc2f --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingLogicalMessageContext.cs @@ -0,0 +1,31 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + + /// + /// A context of behavior execution in logical message processing stage. + /// + public interface IIncomingLogicalMessageContext : IIncomingContext + { + /// + /// Message being handled. + /// + LogicalMessage Message { get; } + + /// + /// Headers for the incoming message. + /// + Dictionary Headers { get; } + + /// + /// Tells if the message has been handled. + /// + bool MessageHandled { get; set; } + + /// + /// Updates the message instance contained in . + /// + /// The new instance. + void UpdateMessageInstance(object newInstance); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPhysicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPhysicalMessageContext.cs new file mode 100644 index 00000000000..6cdfedfc35b --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPhysicalMessageContext.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Pipeline +{ + using Transport; + + /// + /// A context of behavior execution in physical message processing stage. + /// + public interface IIncomingPhysicalMessageContext : IIncomingContext + { + /// + /// The physical message being processed. + /// + IncomingMessage Message { get; } + + /// + /// Updates the message with the given body. + /// + void UpdateMessage(byte[] body); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IInvokeHandlerContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IInvokeHandlerContext.cs new file mode 100644 index 00000000000..134327306d0 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IInvokeHandlerContext.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Unicast.Messages; + + /// + /// A context of handling a logical message by a handler. + /// + public interface IInvokeHandlerContext : IIncomingContext, IMessageHandlerContext + { + /// + /// The current being executed. + /// + MessageHandler MessageHandler { get; } + + /// + /// Message headers. + /// + Dictionary Headers { get; } + + /// + /// The message instance being handled. + /// + object MessageBeingHandled { get; } + + /// + /// Indicates whether has been called. + /// + bool HandleCurrentMessageLaterWasCalled { get; } + + /// + /// true if or + /// has been called. + /// + bool HandlerInvocationAborted { get; } + + /// + /// Metadata for the incoming message. + /// + MessageMetadata MessageMetadata { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/ITransportReceiveContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/ITransportReceiveContext.cs new file mode 100644 index 00000000000..73d8849d327 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/ITransportReceiveContext.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Pipeline +{ + using Transport; + + /// + /// Context containing a physical message. + /// + public interface ITransportReceiveContext : IBehaviorContext + { + /// + /// The physical message being processed. + /// + IncomingMessage Message { get; } + + /// + /// Allows the pipeline to flag that it has been aborted and the receive operation should be rolled back. + /// + void AbortReceiveOperation(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingContext.cs new file mode 100644 index 00000000000..26cc1334bed --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingContext.cs @@ -0,0 +1,69 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Pipeline; + + abstract class IncomingContext : BehaviorContext, IIncomingContext + { + protected IncomingContext(string messageId, string replyToAddress, IReadOnlyDictionary headers, IBehaviorContext parentContext) + : base(parentContext) + { + MessageId = messageId; + ReplyToAddress = replyToAddress; + MessageHeaders = headers; + } + + public string MessageId { get; } + + public string ReplyToAddress { get; } + + public IReadOnlyDictionary MessageHeaders { get; } + + public Task Send(object message, SendOptions options) + { + return MessageOperations.Send(this, message, options); + } + + public Task Send(Action messageConstructor, SendOptions options) + { + return MessageOperations.Send(this, messageConstructor, options); + } + + public Task Publish(object message, PublishOptions options) + { + return MessageOperations.Publish(this, message, options); + } + + public Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + return MessageOperations.Publish(this, messageConstructor, publishOptions); + } + + public Task Reply(object message, ReplyOptions options) + { + return MessageOperations.Reply(this, message, options); + } + + public Task Reply(Action messageConstructor, ReplyOptions options) + { + return MessageOperations.Reply(this, messageConstructor, options); + } + + public Task ForwardCurrentMessageTo(string destination) + { + return IncomingMessageOperations.ForwardCurrentMessageTo(this, destination); + } + + public Task Subscribe(Type eventType, SubscribeOptions options) + { + return MessageOperations.Subscribe(this, eventType, options); + } + + public Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + return MessageOperations.Unsubscribe(this, eventType, options); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingLogicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingLogicalMessageContext.cs new file mode 100644 index 00000000000..4209860395f --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingLogicalMessageContext.cs @@ -0,0 +1,45 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + + class IncomingLogicalMessageContext : IncomingContext, IIncomingLogicalMessageContext + { + internal IncomingLogicalMessageContext(LogicalMessage logicalMessage, IIncomingPhysicalMessageContext parentContext) + : this(logicalMessage, parentContext.MessageId, parentContext.ReplyToAddress, parentContext.Message.Headers, parentContext) + { + } + + public IncomingLogicalMessageContext(LogicalMessage logicalMessage, string messageId, string replyToAddress, Dictionary headers, IBehaviorContext parentContext) + : base(messageId, replyToAddress, headers, parentContext) + { + Message = logicalMessage; + Headers = headers; + Set(logicalMessage); + } + + public LogicalMessage Message { get; } + + public Dictionary Headers { get; } + + public bool MessageHandled { get; set; } + + public void UpdateMessageInstance(object newInstance) + { + Guard.AgainstNull(nameof(newInstance), newInstance); + var sameInstance = ReferenceEquals(Message.Instance, newInstance); + + Message.Instance = newInstance; + + if (sameInstance) + { + return; + } + + var factory = Builder.Build(); + var newLogicalMessage = factory.Create(newInstance); + + Message.Metadata = newLogicalMessage.Metadata; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPhysicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPhysicalMessageContext.cs new file mode 100644 index 00000000000..1ba7bd0ea09 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPhysicalMessageContext.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using Pipeline; + using Transport; + + class IncomingPhysicalMessageContext : IncomingContext, IIncomingPhysicalMessageContext + { + public IncomingPhysicalMessageContext(IncomingMessage message, IBehaviorContext parentContext) + : base(message.MessageId, message.GetReplyToAddress(), message.Headers, parentContext) + { + Message = message; + } + + public IncomingMessage Message { get; } + + public void UpdateMessage(byte[] body) + { + Message.UpdateBody(body); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerContext.cs new file mode 100644 index 00000000000..8eaffd47ff7 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerContext.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Persistence; + using Pipeline; + using Unicast.Messages; + + class InvokeHandlerContext : IncomingContext, IInvokeHandlerContext + { + internal InvokeHandlerContext(MessageHandler handler, SynchronizedStorageSession storageSession, IIncomingLogicalMessageContext parentContext) + : this(handler, parentContext.MessageId, parentContext.ReplyToAddress, parentContext.Headers, parentContext.Message.Metadata, parentContext.Message.Instance, storageSession, parentContext) + { + } + + public InvokeHandlerContext(MessageHandler handler, string messageId, string replyToAddress, Dictionary headers, MessageMetadata messageMetadata, object messageBeingHandled, SynchronizedStorageSession storageSession, IBehaviorContext parentContext) + : base(messageId, replyToAddress, headers, parentContext) + { + MessageHandler = handler; + Headers = headers; + MessageBeingHandled = messageBeingHandled; + MessageMetadata = messageMetadata; + Set(storageSession); + } + + public MessageHandler MessageHandler { get; } + + public SynchronizedStorageSession SynchronizedStorageSession => Get(); + + public Dictionary Headers { get; } + + public object MessageBeingHandled { get; } + + public bool HandlerInvocationAborted { get; private set; } + + public MessageMetadata MessageMetadata { get; } + + public bool HandleCurrentMessageLaterWasCalled { get; private set; } + + public async Task HandleCurrentMessageLater() + { + await MessageOperationsInvokeHandlerContext.HandleCurrentMessageLater(this).ConfigureAwait(false); + HandleCurrentMessageLaterWasCalled = true; + DoNotContinueDispatchingCurrentMessageToHandlers(); + } + + public void DoNotContinueDispatchingCurrentMessageToHandlers() + { + HandlerInvocationAborted = true; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs new file mode 100644 index 00000000000..888d9b6658e --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using System.Transactions; + using Pipeline; + using Sagas; + + class InvokeHandlerTerminator : PipelineTerminator + { + protected override Task Terminate(IInvokeHandlerContext context) + { + context.Extensions.Set(new State + { + ScopeWasPresent = Transaction.Current != null + }); + + ActiveSagaInstance saga; + + if (context.Extensions.TryGet(out saga) && saga.NotFound && saga.Metadata.SagaType == context.MessageHandler.Instance.GetType()) + { + return TaskEx.CompletedTask; + } + + var messageHandler = context.MessageHandler; + + return messageHandler + .Invoke(context.MessageBeingHandled, context) + .ThrowIfNull(); + } + + public class State + { + public bool ScopeWasPresent { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs new file mode 100644 index 00000000000..a4a46be34bc --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs @@ -0,0 +1,66 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Extensibility; + using Outbox; + using Persistence; + using Pipeline; + using Transport; + using Unicast; + + class LoadHandlersConnector : StageConnector + { + public LoadHandlersConnector(MessageHandlerRegistry messageHandlerRegistry, ISynchronizedStorage synchronizedStorage, ISynchronizedStorageAdapter adapter) + { + this.messageHandlerRegistry = messageHandlerRegistry; + this.synchronizedStorage = synchronizedStorage; + this.adapter = adapter; + } + + public override async Task Invoke(IIncomingLogicalMessageContext context, Func stage) + { + var outboxTransaction = context.Extensions.Get(); + var transportTransaction = context.Extensions.Get(); + using (var storageSession = await AdaptOrOpenNewSynchronizedStorageSession(transportTransaction, outboxTransaction, context.Extensions).ConfigureAwait(false)) + { + var handlersToInvoke = messageHandlerRegistry.GetHandlersFor(context.Message.MessageType); + + if (!context.MessageHandled && handlersToInvoke.Count == 0) + { + var error = $"No handlers could be found for message type: {context.Message.MessageType}"; + throw new InvalidOperationException(error); + } + + foreach (var messageHandler in handlersToInvoke) + { + messageHandler.Instance = context.Builder.Build(messageHandler.HandlerType); + + var handlingContext = this.CreateInvokeHandlerContext(messageHandler, storageSession, context); + await stage(handlingContext).ConfigureAwait(false); + + if (handlingContext.HandlerInvocationAborted) + { + //if the chain was aborted skip the other handlers + break; + } + } + context.MessageHandled = true; + await storageSession.CompleteAsync().ConfigureAwait(false); + } + } + + async Task AdaptOrOpenNewSynchronizedStorageSession(TransportTransaction transportTransaction, OutboxTransaction outboxTransaction, ContextBag contextBag) + { + return await adapter.TryAdapt(outboxTransaction, contextBag).ConfigureAwait(false) + ?? await adapter.TryAdapt(transportTransaction, contextBag).ConfigureAwait(false) + ?? await synchronizedStorage.OpenSession(contextBag).ConfigureAwait(false); + } + + readonly ISynchronizedStorageAdapter adapter; + readonly ISynchronizedStorage synchronizedStorage; + + + MessageHandlerRegistry messageHandlerRegistry; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessage.cs b/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessage.cs new file mode 100644 index 00000000000..36d2fa1b985 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessage.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.Pipeline +{ + using System; + using Unicast.Messages; + + /// + /// The logical message. + /// + public class LogicalMessage + { + /// + /// Create a new instance containing a message object and it's corresponding . + /// + public LogicalMessage(MessageMetadata metadata, object message) + { + Instance = message; + Metadata = metadata; + } + + /// + /// The of the message instance. + /// + public Type MessageType => Metadata.MessageType; + + + /// + /// Message metadata. + /// + public MessageMetadata Metadata { get; internal set; } + + /// + /// The message instance. + /// + public object Instance { get; internal set; } + + /// + /// Updates the message instance. + /// + /// The new instance. + [ObsoleteEx( + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6", + ReplacementTypeOrMember = "IIncomingLogicalMessageContext.UpdateMessageInstance(object newInstance)")] + public void UpdateMessageInstance(object newInstance) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessageFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessageFactory.cs new file mode 100644 index 00000000000..2ec9fd1f485 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/LogicalMessageFactory.cs @@ -0,0 +1,57 @@ +namespace NServiceBus.Pipeline +{ + using System; + using MessageInterfaces; + using Unicast.Messages; + + /// + /// Factory to create s. + /// + public class LogicalMessageFactory + { + /// + /// Initializes a new instance of . + /// + public LogicalMessageFactory(MessageMetadataRegistry messageMetadataRegistry, IMessageMapper messageMapper) + { + this.messageMetadataRegistry = messageMetadataRegistry; + this.messageMapper = messageMapper; + } + + /// + /// Creates a new using the specified message instance. + /// + /// The message instance. + /// A new . + public LogicalMessage Create(object message) + { + Guard.AgainstNull(nameof(message), message); + + return Create(message.GetType(), message); + } + + /// + /// Creates a new using the specified messageType, message instance and headers. + /// + /// The message type. + /// The message instance. + /// A new . + public LogicalMessage Create(Type messageType, object message) + { + Guard.AgainstNull(nameof(messageType), messageType); + Guard.AgainstNull(nameof(message), message); + + if (messageType == null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + var realMessageType = messageMapper.GetMappedTypeFor(messageType); + + return new LogicalMessage(messageMetadataRegistry.GetMessageMetadata(realMessageType), message); + } + + IMessageMapper messageMapper; + MessageMetadataRegistry messageMetadataRegistry; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs b/src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs new file mode 100644 index 00000000000..894767b167c --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs @@ -0,0 +1,44 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents a message handler and its invocation. + /// + public class MessageHandler + { + /// + /// Creates a new instance of the message handler with predefined invocation delegate and handler type. + /// + /// The invocation with context delegate. + /// The handler type. + public MessageHandler(Func invocation, Type handlerType) + { + HandlerType = handlerType; + this.invocation = invocation; + } + + /// + /// The actual instance, can be a saga, a timeout or just a plain handler. + /// + public object Instance { get; set; } + + /// + /// The handler type, can be a saga, a timeout or just a plain handler. + /// + public Type HandlerType { get; private set; } + + /// + /// Invokes the message handler. + /// + /// the message to pass to the handler. + /// the context to pass to the handler. + public Task Invoke(object message, IMessageHandlerContext handlerContext) + { + return invocation(Instance, message, handlerContext); + } + + Func invocation; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/PendingTransportOperations.cs b/src/NServiceBus.Core/Pipeline/Incoming/PendingTransportOperations.cs new file mode 100644 index 00000000000..63cc1b0f764 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/PendingTransportOperations.cs @@ -0,0 +1,47 @@ +namespace NServiceBus +{ + using System.Collections.Concurrent; + using Transport; + + /// + /// Represents the currently pending transport operations. The transport operations that are collected here will be + /// dispatched in the batched dispatch stage of the pipeline. + /// + /// This class is threadsafe. + public class PendingTransportOperations + { + /// + /// Gets the currently pending transport operations. + /// + public TransportOperation[] Operations => operations.ToArray(); + + /// + /// Indicates whether there are transport operations pending. + /// + public bool HasOperations => !operations.IsEmpty; + + /// + /// Adds a transport operation. + /// + /// The transport operation to be added. + public void Add(TransportOperation transportOperation) + { + Guard.AgainstNull(nameof(transportOperation), transportOperation); + + operations.Push(transportOperation); + } + + /// + /// Adds a range of transport operations. + /// + /// The transport operations to be added. + public void AddRange(TransportOperation[] transportOperations) + { + Guard.AgainstNullAndEmpty(nameof(transportOperations), transportOperations); + + operations.PushRange(transportOperations); + } + + ConcurrentStack operations = new ConcurrentStack(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/ReceiveFeature.cs b/src/NServiceBus.Core/Pipeline/Incoming/ReceiveFeature.cs new file mode 100644 index 00000000000..d3f88ddd936 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/ReceiveFeature.cs @@ -0,0 +1,119 @@ +namespace NServiceBus.Features +{ + using System.Threading.Tasks; + using Extensibility; + using Janitor; + using NServiceBus.Outbox; + using Persistence; + using Transport; + using Unicast; + + class ReceiveFeature : Feature + { + public ReceiveFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Pipeline.Register("TransportReceiveToPhysicalMessageProcessingConnector", b => b.Build(), "Allows to abort processing the message"); + context.Pipeline.Register("LoadHandlersConnector", b => b.Build(), "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); + + context.Pipeline.Register("ExecuteUnitOfWork", new UnitOfWorkBehavior(), "Executes the UoW"); + context.Pipeline.Register("MutateIncomingTransportMessage", new MutateIncomingTransportMessageBehavior(), "Executes IMutateIncomingTransportMessages"); + context.Pipeline.Register("MutateIncomingMessages", new MutateIncomingMessageBehavior(), "Executes IMutateIncomingMessages"); + context.Pipeline.Register("InvokeHandlers", new InvokeHandlerTerminator(), "Calls the IHandleMessages.Handle(T)"); + + context.Container.ConfigureComponent(b => + { + var storage = context.Container.HasComponent() ? b.Build() : new NoOpOutbox(); + + return new TransportReceiveToPhysicalMessageProcessingConnector(storage); + }, DependencyLifecycle.InstancePerCall); + + context.Container.ConfigureComponent(b => + { + var adapter = context.Container.HasComponent() ? b.Build() : new NoOpAdaper(); + var syncStorage = context.Container.HasComponent() ? b.Build() : new NoOpSynchronizedStorage(); + + return new LoadHandlersConnector(b.Build(), syncStorage, adapter); + }, DependencyLifecycle.InstancePerCall); + } + + class NoOpSynchronizedStorage : ISynchronizedStorage + { + public Task OpenSession(ContextBag contextBag) + { + return NoOpAdaper.EmptyResult; + } + } + + class NoOpAdaper : ISynchronizedStorageAdapter + { + public Task TryAdapt(OutboxTransaction transaction, ContextBag context) + { + return EmptyResult; + } + + public Task TryAdapt(TransportTransaction transportTransaction, ContextBag context) + { + return EmptyResult; + } + + internal static readonly Task EmptyResult = Task.FromResult(new NoOpCompletableSynchronizedStorageSession()); + } + + // Do not allow Fody to weave the IDisposable for us so that other threads can still access the instance of this class + // even after it has been disposed. + [SkipWeaving] + class NoOpCompletableSynchronizedStorageSession : CompletableSynchronizedStorageSession + { + public Task CompleteAsync() + { + return TaskEx.CompletedTask; + } + + public void Dispose() + { + } + } + + class NoOpOutbox : IOutboxStorage + { + public Task Get(string messageId, ContextBag options) + { + return NoOutboxMessageTask; + } + + public Task Store(OutboxMessage message, OutboxTransaction transaction, ContextBag options) + { + return TaskEx.CompletedTask; + } + + public Task SetAsDispatched(string messageId, ContextBag options) + { + return TaskEx.CompletedTask; + } + + public Task BeginTransaction(ContextBag context) + { + return Task.FromResult(new NoOpOutboxTransaction()); + } + + static Task NoOutboxMessageTask = Task.FromResult(null); + } + + class NoOpOutboxTransaction : OutboxTransaction + { + public void Dispose() + { + } + + public Task Commit() + { + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs new file mode 100644 index 00000000000..3b45bbce5a7 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs @@ -0,0 +1,46 @@ +namespace NServiceBus +{ + using System.Threading; + using Pipeline; + using Transport; + + /// + /// Context containing a physical message. + /// + class TransportReceiveContext : BehaviorContext, ITransportReceiveContext + { + /// + /// Creates a new transport receive context. + /// + /// The received message. + /// The transport transaction. + /// + /// Allows the pipeline to flag that it has been aborted and the receive operation should be rolled back. + /// It also allows the transport to communicate to the pipeline to abort if possible. + /// + /// The parent context. + public TransportReceiveContext(IncomingMessage receivedMessage, TransportTransaction transportTransaction, CancellationTokenSource cancellationTokenSource, IBehaviorContext parentContext) + : base(parentContext) + { + this.cancellationTokenSource = cancellationTokenSource; + Message = receivedMessage; + Set(Message); + Set(transportTransaction); + } + + /// + /// The physical message being processed. + /// + public IncomingMessage Message { get; } + + /// + /// Allows the pipeline to flag that it has been aborted and the receive operation should be rolled back. + /// + public void AbortReceiveOperation() + { + cancellationTokenSource.Cancel(); + } + + CancellationTokenSource cancellationTokenSource; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageProcessingConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageProcessingConnector.cs new file mode 100644 index 00000000000..4ca0bd50671 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageProcessingConnector.cs @@ -0,0 +1,201 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using Outbox; + using Performance.TimeToBeReceived; + using Pipeline; + using Routing; + using Transport; + using TransportOperation = Outbox.TransportOperation; + + class TransportReceiveToPhysicalMessageProcessingConnector : IStageForkConnector + { + public TransportReceiveToPhysicalMessageProcessingConnector(IOutboxStorage outboxStorage) + { + this.outboxStorage = outboxStorage; + } + + public async Task Invoke(ITransportReceiveContext context, Func next) + { + var messageId = context.Message.MessageId; + var physicalMessageContext = this.CreateIncomingPhysicalMessageContext(context.Message, context); + + var deduplicationEntry = await outboxStorage.Get(messageId, context.Extensions).ConfigureAwait(false); + var pendingTransportOperations = new PendingTransportOperations(); + + if (deduplicationEntry == null) + { + physicalMessageContext.Extensions.Set(pendingTransportOperations); + + using (var outboxTransaction = await outboxStorage.BeginTransaction(context.Extensions).ConfigureAwait(false)) + { + context.Extensions.Set(outboxTransaction); + await next(physicalMessageContext).ConfigureAwait(false); + + var outboxMessage = new OutboxMessage(messageId, ConvertToOutboxOperations(pendingTransportOperations.Operations)); + await outboxStorage.Store(outboxMessage, outboxTransaction, context.Extensions).ConfigureAwait(false); + + context.Extensions.Remove(); + await outboxTransaction.Commit().ConfigureAwait(false); + } + + physicalMessageContext.Extensions.Remove(); + } + else + { + ConvertToPendingOperations(deduplicationEntry, pendingTransportOperations); + } + + if (pendingTransportOperations.HasOperations) + { + var batchDispatchContext = this.CreateBatchDispatchContext(pendingTransportOperations.Operations, physicalMessageContext); + + await this.Fork(batchDispatchContext).ConfigureAwait(false); + } + + await outboxStorage.SetAsDispatched(messageId, context.Extensions).ConfigureAwait(false); + } + + static void ConvertToPendingOperations(OutboxMessage deduplicationEntry, PendingTransportOperations pendingTransportOperations) + { + foreach (var operation in deduplicationEntry.TransportOperations) + { + var message = new OutgoingMessage(operation.MessageId, operation.Headers, operation.Body); + + pendingTransportOperations.Add( + new Transport.TransportOperation( + message, + DeserializeRoutingStrategy(operation.Options), + DispatchConsistency.Isolated, + DeserializeConstraints(operation.Options))); + } + } + + static TransportOperation[] ConvertToOutboxOperations(Transport.TransportOperation[] operations) + { + var transportOperations = new TransportOperation[operations.Length]; + var index = 0; + foreach (var operation in operations) + { + var options = new Dictionary(); + + foreach (var constraint in operation.DeliveryConstraints) + { + SerializeDeliveryConstraint(constraint, options); + } + + SerializeRoutingStrategy(operation.AddressTag, options); + + transportOperations[index] = new TransportOperation(operation.Message.MessageId, options, operation.Message.Body, operation.Message.Headers); + index++; + } + return transportOperations; + } + + static void SerializeRoutingStrategy(AddressTag addressTag, Dictionary options) + { + var indirect = addressTag as MulticastAddressTag; + if (indirect != null) + { + options["EventType"] = indirect.MessageType.AssemblyQualifiedName; + return; + } + + var direct = addressTag as UnicastAddressTag; + if (direct != null) + { + options["Destination"] = direct.Destination; + return; + } + + throw new Exception($"Unknown routing strategy {addressTag.GetType().FullName}"); + } + + static void SerializeDeliveryConstraint(DeliveryConstraint constraint, Dictionary options) + { + var nonDurable = constraint as NonDurableDelivery; + if (nonDurable != null) + { + options["NonDurable"] = true.ToString(); + return; + } + var doNotDeliverBefore = constraint as DoNotDeliverBefore; + if (doNotDeliverBefore != null) + { + options["DeliverAt"] = DateTimeExtensions.ToWireFormattedString(doNotDeliverBefore.At); + return; + } + + var delayDeliveryWith = constraint as DelayDeliveryWith; + if (delayDeliveryWith != null) + { + options["DelayDeliveryFor"] = delayDeliveryWith.Delay.ToString(); + return; + } + + var discard = constraint as DiscardIfNotReceivedBefore; + if (discard != null) + { + options["TimeToBeReceived"] = discard.MaxTime.ToString(); + return; + } + + throw new Exception($"Unknown delivery constraint {constraint.GetType().FullName}"); + } + + static List DeserializeConstraints(Dictionary options) + { + var constraints = new List(4); + if (options.ContainsKey("NonDurable")) + { + constraints.Add(new NonDurableDelivery()); + } + + string deliverAt; + if (options.TryGetValue("DeliverAt", out deliverAt)) + { + constraints.Add(new DoNotDeliverBefore(DateTimeExtensions.ToUtcDateTime(deliverAt))); + } + + + string delay; + if (options.TryGetValue("DelayDeliveryFor", out delay)) + { + constraints.Add(new DelayDeliveryWith(TimeSpan.Parse(delay))); + } + + string ttbr; + + if (options.TryGetValue("TimeToBeReceived", out ttbr)) + { + constraints.Add(new DiscardIfNotReceivedBefore(TimeSpan.Parse(ttbr))); + } + return constraints; + } + + static AddressTag DeserializeRoutingStrategy(Dictionary options) + { + string destination; + + if (options.TryGetValue("Destination", out destination)) + { + return new UnicastAddressTag(destination); + } + + string eventType; + + if (options.TryGetValue("EventType", out eventType)) + { + return new MulticastAddressTag(Type.GetType(eventType, true)); + } + + throw new Exception("Could not find routing strategy to deserialize"); + } + + IOutboxStorage outboxStorage; + } +} diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs new file mode 100644 index 00000000000..b32ac246b43 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -0,0 +1,43 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using ObjectBuilder; + using Pipeline; + using Transport; + + class MainPipelineExecutor : IPipelineExecutor + { + public MainPipelineExecutor(IBuilder builder, IEventAggregator eventAggregator, IPipelineCache pipelineCache, IPipeline mainPipeline) + { + this.mainPipeline = mainPipeline; + this.pipelineCache = pipelineCache; + this.builder = builder; + this.eventAggregator = eventAggregator; + } + + public async Task Invoke(MessageContext messageContext) + { + var pipelineStartedAt = DateTime.UtcNow; + + using (var childBuilder = builder.CreateChildBuilder()) + { + var rootContext = new RootContext(childBuilder, pipelineCache, eventAggregator); + + var message = new IncomingMessage(messageContext.MessageId, messageContext.Headers, messageContext.Body); + var context = new TransportReceiveContext(message, messageContext.TransportTransaction, messageContext.ReceiveCancellationTokenSource, rootContext); + + context.Extensions.Merge(messageContext.Context); + + await mainPipeline.Invoke(context).ConfigureAwait(false); + + await context.RaiseNotification(new ReceivePipelineCompleted(message, pipelineStartedAt, DateTime.UtcNow)).ConfigureAwait(false); + } + } + + IEventAggregator eventAggregator; + IBuilder builder; + IPipelineCache pipelineCache; + IPipeline mainPipeline; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateIncomingMessages.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateIncomingMessages.cs new file mode 100644 index 00000000000..3bdbfedf7a5 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateIncomingMessages.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.MessageMutator +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Mutates incoming messages. + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IMutateIncomingMessages + { + /// + /// Mutates the given message right after it has been deserialized. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task MutateIncoming(MutateIncomingMessageContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateOutgoingMessages.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateOutgoingMessages.cs new file mode 100644 index 00000000000..cb8731ab5bd --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/IMutateOutgoingMessages.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.MessageMutator +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Mutates outgoing messages. + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IMutateOutgoingMessages + { + /// + /// Mutates the given message just before it's serialized. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task MutateOutgoing(MutateOutgoingMessageContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehavior.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehavior.cs new file mode 100644 index 00000000000..cf19e8127ff --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageBehavior.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using MessageMutator; + using Pipeline; + + class MutateIncomingMessageBehavior : IBehavior + { + public async Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + var logicalMessage = context.Message; + var current = logicalMessage.Instance; + + var mutatorContext = new MutateIncomingMessageContext(current, context.Headers); + foreach (var mutator in context.Builder.BuildAll()) + { + await mutator.MutateIncoming(mutatorContext) + .ThrowIfNull() + .ConfigureAwait(false); + } + + if (mutatorContext.MessageInstanceChanged) + { + context.UpdateMessageInstance(mutatorContext.Message); + } + + await next(context).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageContext.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageContext.cs new file mode 100644 index 00000000000..be830a27b93 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateIncomingMessageContext.cs @@ -0,0 +1,44 @@ +namespace NServiceBus.MessageMutator +{ + using System.Collections.Generic; + + /// + /// Provides ways to mutate the outgoing message instance. + /// + public class MutateIncomingMessageContext + { + /// + /// Initializes the context. + /// + public MutateIncomingMessageContext(object message, Dictionary headers) + { + Guard.AgainstNull(nameof(headers), headers); + Guard.AgainstNull(nameof(message), message); + Headers = headers; + this.message = message; + } + + /// + /// The current incoming message. + /// + public object Message + { + get { return message; } + set + { + Guard.AgainstNull(nameof(value), value); + MessageInstanceChanged = true; + message = value; + } + } + + /// + /// The current incoming headers. + /// + public Dictionary Headers { get; private set; } + + object message; + + internal bool MessageInstanceChanged; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehavior.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehavior.cs new file mode 100644 index 00000000000..d1122f875ad --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageBehavior.cs @@ -0,0 +1,40 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using MessageMutator; + using Pipeline; + using Transport; + + class MutateOutgoingMessageBehavior : IBehavior + { + public async Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + LogicalMessage incomingLogicalMessage; + context.Extensions.TryGet(out incomingLogicalMessage); + + IncomingMessage incomingPhysicalMessage; + context.Extensions.TryGet(out incomingPhysicalMessage); + + var mutatorContext = new MutateOutgoingMessageContext( + context.Message.Instance, + context.Headers, + incomingLogicalMessage?.Instance, + incomingPhysicalMessage?.Headers); + + foreach (var mutator in context.Builder.BuildAll()) + { + await mutator.MutateOutgoing(mutatorContext) + .ThrowIfNull() + .ConfigureAwait(false); + } + + if (mutatorContext.MessageInstanceChanged) + { + context.UpdateMessage(mutatorContext.OutgoingMessage); + } + + await next(context).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageContext.cs b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageContext.cs new file mode 100644 index 00000000000..bc5c7cc0122 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateInstanceMessage/MutateOutgoingMessageContext.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.MessageMutator +{ + using System.Collections.Generic; + + /// + /// Provides ways to mutate the outgoing message instance. + /// + public class MutateOutgoingMessageContext + { + /// + /// Initializes the context. + /// + public MutateOutgoingMessageContext(object outgoingMessage, Dictionary outgoingHeaders, object incomingMessage, IReadOnlyDictionary incomingHeaders) + { + Guard.AgainstNull(nameof(outgoingHeaders), outgoingHeaders); + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + OutgoingHeaders = outgoingHeaders; + this.incomingMessage = incomingMessage; + this.incomingHeaders = incomingHeaders; + this.outgoingMessage = outgoingMessage; + } + + /// + /// The current outgoing message. + /// + public object OutgoingMessage + { + get { return outgoingMessage; } + set + { + Guard.AgainstNull(nameof(value), value); + MessageInstanceChanged = true; + outgoingMessage = value; + } + } + + /// + /// The current outgoing headers. + /// + public Dictionary OutgoingHeaders { get; private set; } + + /// + /// Gets the incoming message that initiated the current send if it exists. + /// + public bool TryGetIncomingMessage(out object incomingMessage) + { + incomingMessage = this.incomingMessage; + return incomingMessage != null; + } + + /// + /// Gets the incoming headers that initiated the current send if it exists. + /// + public bool TryGetIncomingHeaders(out IReadOnlyDictionary incomingHeaders) + { + incomingHeaders = this.incomingHeaders; + return incomingHeaders != null; + } + + IReadOnlyDictionary incomingHeaders; + object incomingMessage; + + internal bool MessageInstanceChanged; + + object outgoingMessage; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateIncomingTransportMessages.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateIncomingTransportMessages.cs new file mode 100644 index 00000000000..e53e5f3f639 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateIncomingTransportMessages.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.MessageMutator +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Mutates transport messages when they are received. + /// Implementations are invoked before the logical messages have been deserialized. + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IMutateIncomingTransportMessages + { + /// + /// Modifies various properties of the transport message. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task MutateIncoming(MutateIncomingTransportMessageContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateOutgoingTransportMessages.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateOutgoingTransportMessages.cs new file mode 100644 index 00000000000..d5943db5c16 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/IMutateOutgoingTransportMessages.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.MessageMutator +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Provides a way to mutate the context for outgoing messages in the physical stage. + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IMutateOutgoingTransportMessages + { + /// + /// Performs the mutation. + /// + /// Contains information about the current message and provides ways to mutate it. + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task MutateOutgoing(MutateOutgoingTransportMessageContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs new file mode 100644 index 00000000000..c7392edd283 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using MessageMutator; + using Pipeline; + + class MutateIncomingTransportMessageBehavior : IBehavior + { + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + var mutators = context.Builder.BuildAll(); + var transportMessage = context.Message; + var mutatorContext = new MutateIncomingTransportMessageContext(transportMessage.Body, transportMessage.Headers); + foreach (var mutator in mutators) + { + await mutator.MutateIncoming(mutatorContext) + .ThrowIfNull() + .ConfigureAwait(false); + } + + if (mutatorContext.MessageBodyChanged) + { + context.UpdateMessage(mutatorContext.Body); + } + + await next(context).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageContext.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageContext.cs new file mode 100644 index 00000000000..bb2c639ae6f --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateIncomingTransportMessageContext.cs @@ -0,0 +1,46 @@ +namespace NServiceBus.MessageMutator +{ + using System.Collections.Generic; + + /// + /// Context class for . + /// + public class MutateIncomingTransportMessageContext + { + /// + /// Initializes a new instance of . + /// + public MutateIncomingTransportMessageContext(byte[] body, Dictionary headers) + { + Guard.AgainstNull(nameof(headers), headers); + Guard.AgainstNull(nameof(body), body); + Headers = headers; + + // Intentionally assign to field to not set the MessageBodyChanged flag. + this.body = body; + } + + /// + /// The body of the message. + /// + public byte[] Body + { + get { return body; } + set + { + Guard.AgainstNull(nameof(value), value); + MessageBodyChanged = true; + body = value; + } + } + + /// + /// The current incoming headers. + /// + public Dictionary Headers { get; private set; } + + byte[] body; + + internal bool MessageBodyChanged; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehavior.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehavior.cs new file mode 100644 index 00000000000..39d4eb269f9 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageBehavior.cs @@ -0,0 +1,43 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using MessageMutator; + using Pipeline; + using Transport; + + class MutateOutgoingTransportMessageBehavior : IBehavior + { + public async Task Invoke(IOutgoingPhysicalMessageContext context, Func next) + { + var outgoingMessage = context.Extensions.Get(); + + LogicalMessage incomingLogicalMessage; + context.Extensions.TryGet(out incomingLogicalMessage); + + IncomingMessage incomingPhysicalMessage; + context.Extensions.TryGet(out incomingPhysicalMessage); + + var mutatorContext = new MutateOutgoingTransportMessageContext( + context.Body, + outgoingMessage.Instance, + context.Headers, + incomingLogicalMessage?.Instance, + incomingPhysicalMessage?.Headers); + + foreach (var mutator in context.Builder.BuildAll()) + { + await mutator.MutateOutgoing(mutatorContext) + .ThrowIfNull() + .ConfigureAwait(false); + } + + if (mutatorContext.MessageBodyChanged) + { + context.UpdateMessage(mutatorContext.OutgoingBody); + } + + await next(context).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageContext.cs b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageContext.cs new file mode 100644 index 00000000000..f9bbc6ffa52 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/MutateTransportMessage/MutateOutgoingTransportMessageContext.cs @@ -0,0 +1,75 @@ +namespace NServiceBus.MessageMutator +{ + using System.Collections.Generic; + + /// + /// Context class for . + /// + public class MutateOutgoingTransportMessageContext + { + /// + /// Initializes a new instance of . + /// + public MutateOutgoingTransportMessageContext(byte[] outgoingBody, object outgoingMessage, Dictionary outgoingHeaders, object incomingMessage, IReadOnlyDictionary incomingHeaders) + { + Guard.AgainstNull(nameof(outgoingHeaders), outgoingHeaders); + Guard.AgainstNull(nameof(outgoingBody), outgoingBody); + Guard.AgainstNull(nameof(outgoingMessage), outgoingMessage); + + OutgoingHeaders = outgoingHeaders; + // Intentionally assign to field to not set the MessageBodyChanged flag. + this.outgoingBody = outgoingBody; + OutgoingMessage = outgoingMessage; + this.incomingHeaders = incomingHeaders; + this.incomingMessage = incomingMessage; + } + + /// + /// The current outgoing message. + /// + public object OutgoingMessage { get; } + + /// + /// The body of the message. + /// + public byte[] OutgoingBody + { + get { return outgoingBody; } + set + { + Guard.AgainstNull(nameof(value), value); + MessageBodyChanged = true; + outgoingBody = value; + } + } + + /// + /// The current outgoing headers. + /// + public Dictionary OutgoingHeaders { get; } + + /// + /// Gets the incoming message that initiated the current send if it exists. + /// + public bool TryGetIncomingMessage(out object incomingMessage) + { + incomingMessage = this.incomingMessage; + return incomingMessage != null; + } + + /// + /// Gets the incoming headers that initiated the current send if it exists. + /// + public bool TryGetIncomingHeaders(out IReadOnlyDictionary incomingHeaders) + { + incomingHeaders = this.incomingHeaders; + return incomingHeaders != null; + } + + IReadOnlyDictionary incomingHeaders; + object incomingMessage; + + internal bool MessageBodyChanged; + byte[] outgoingBody; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageBehavior.cs b/src/NServiceBus.Core/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageBehavior.cs new file mode 100644 index 00000000000..ad91cd0f4eb --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/AttachSenderRelatedInfoOnMessageBehavior.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class AttachSenderRelatedInfoOnMessageBehavior : IBehavior + { + public Task Invoke(IRoutingContext context, Func next) + { + var message = context.Message; + + if (!message.Headers.ContainsKey(Headers.NServiceBusVersion)) + { + message.Headers[Headers.NServiceBusVersion] = GitFlowVersion.MajorMinorPatch; + } + + if (!message.Headers.ContainsKey(Headers.TimeSent)) + { + message.Headers[Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); + } + return next(context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/BatchDispatchContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/BatchDispatchContext.cs new file mode 100644 index 00000000000..b5ad9bbadb8 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/BatchDispatchContext.cs @@ -0,0 +1,17 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + using Transport; + + class BatchDispatchContext : BehaviorContext, IBatchDispatchContext + { + public BatchDispatchContext(IReadOnlyCollection operations, IBehaviorContext parentContext) + : base(parentContext) + { + Operations = operations; + } + + public IReadOnlyCollection Operations { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/BatchToDispatchConnector.cs b/src/NServiceBus.Core/Pipeline/Outgoing/BatchToDispatchConnector.cs new file mode 100644 index 00000000000..b7e51a68637 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/BatchToDispatchConnector.cs @@ -0,0 +1,14 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class BatchToDispatchConnector : StageConnector + { + public override Task Invoke(IBatchDispatchContext context, Func stage) + { + return stage(this.CreateDispatchContext(context.Operations, context)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/DispatchContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/DispatchContext.cs new file mode 100644 index 00000000000..dc14f2b9701 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/DispatchContext.cs @@ -0,0 +1,17 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + using Transport; + + class DispatchContext : BehaviorContext, IDispatchContext + { + public DispatchContext(IReadOnlyCollection operations, IBehaviorContext parentContext) + : base(parentContext) + { + Operations = operations; + } + + public IEnumerable Operations { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/ForceImmediateDispatchForOperationsInSupressedScopeBehavior.cs b/src/NServiceBus.Core/Pipeline/Outgoing/ForceImmediateDispatchForOperationsInSupressedScopeBehavior.cs new file mode 100644 index 00000000000..20bee887f02 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/ForceImmediateDispatchForOperationsInSupressedScopeBehavior.cs @@ -0,0 +1,45 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using Logging; + using Pipeline; + + [ObsoleteEx(RemoveInVersion = "7")] + class ForceImmediateDispatchForOperationsInSuppressedScopeBehavior : IBehavior + { + public Task Invoke(IRoutingContext context, Func next) + { + var state = context.Extensions.GetOrCreate(); + + //if there is no scope here the user must have suppressed it + if (state.ScopeWasPresent && Transaction.Current == null) + { + var dispatchState = context.Extensions.GetOrCreate(); + + if (!dispatchState.ImmediateDispatch) + { + Logger.Warn(scopeWarning); + + dispatchState.ImmediateDispatch = true; + } + } + + return next(context); + } + + static string scopeWarning = @" +Suppressed ambient transaction detected when requesting the outgoing operation. +Support for this behavior is deprecated and will be removed in Version 7. The new api for requesting immediate dispatch is: + +var options = new Send|Publish|ReplyOptions(); + +options.RequireImmediateDispatch(); + +session.Send|Publish|Reply(new MyMessage(), options) +"; + + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/HeaderOptionExtensions.cs b/src/NServiceBus.Core/Pipeline/Outgoing/HeaderOptionExtensions.cs new file mode 100644 index 00000000000..f33769b3f21 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/HeaderOptionExtensions.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using Extensibility; + + /// + /// Extensions to the options to provide ways to set message headers. + /// + public static class HeaderOptionExtensions + { + /// + /// Allows headers to be set for the outgoing message. + /// + /// The options to extend. + /// The header key. + /// The header value. + public static void SetHeader(this ExtendableOptions options, string key, string value) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNullAndEmpty(nameof(key), key); + + options.OutgoingHeaders[key] = value; + } + + /// + /// Returns all headers set by on the outgoing message. + /// + public static IReadOnlyDictionary GetHeaders(this ExtendableOptions options) + { + Guard.AgainstNull(nameof(options), options); + + return new ReadOnlyDictionary(options.OutgoingHeaders); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IBatchDispatchContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IBatchDispatchContext.cs new file mode 100644 index 00000000000..b5022b1fa4d --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IBatchDispatchContext.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Transport; + + /// + /// Pipeline context for dispatching pending transport operations captured during message processing. + /// + public interface IBatchDispatchContext : IBehaviorContext + { + /// + /// The captured transport operations to dispatch. + /// + IReadOnlyCollection Operations { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IDispatchContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IDispatchContext.cs new file mode 100644 index 00000000000..c390e1a1a7b --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IDispatchContext.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Transport; + + /// + /// Context for the immediate dispatch part of the pipeline. + /// + public interface IDispatchContext : IBehaviorContext + { + /// + /// The operations to be dispatched to the transport. + /// + IEnumerable Operations { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingContext.cs new file mode 100644 index 00000000000..8118df27bee --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingContext.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + + /// + /// The base interface for everything inside the outgoing pipeline. + /// + public interface IOutgoingContext : IBehaviorContext, IPipelineContext + { + /// + /// The id of the outgoing message. + /// + string MessageId { get; } + + /// + /// The headers of the outgoing message. + /// + Dictionary Headers { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingLogicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingLogicalMessageContext.cs new file mode 100644 index 00000000000..eec525e3613 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingLogicalMessageContext.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Routing; + + /// + /// Outgoing pipeline context. + /// + public interface IOutgoingLogicalMessageContext : IOutgoingContext + { + /// + /// The outgoing message. + /// + OutgoingLogicalMessage Message { get; } + + /// + /// The routing strategies for this message. + /// + IReadOnlyCollection RoutingStrategies { get; } + + /// + /// Updates the message instance. + /// + void UpdateMessage(object newInstance); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPhysicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPhysicalMessageContext.cs new file mode 100644 index 00000000000..42556f805dc --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPhysicalMessageContext.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Routing; + + /// + /// Represent the part of the outgoing pipeline where the message has been serialized to a byte[]. + /// + public interface IOutgoingPhysicalMessageContext : IOutgoingContext + { + /// + /// The serialized body of the outgoing message. + /// + /// + /// A array containing the serialized contents of the outgoing message. + /// + byte[] Body { get; } + + /// + /// The routing strategies for this message. + /// + IReadOnlyCollection RoutingStrategies { get; } + + /// + /// Updates the message with the given body. + /// + void UpdateMessage(byte[] body); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPublishContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPublishContext.cs new file mode 100644 index 00000000000..736589a6523 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingPublishContext.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.Pipeline +{ + /// + /// Pipeline context for publish operations. + /// + public interface IOutgoingPublishContext : IOutgoingContext + { + /// + /// The message to be published. + /// + OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingReplyContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingReplyContext.cs new file mode 100644 index 00000000000..1a510cffd75 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingReplyContext.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.Pipeline +{ + /// + /// Pipeline context for reply operations. + /// + public interface IOutgoingReplyContext : IOutgoingContext + { + /// + /// The reply message. + /// + OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingSendContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingSendContext.cs new file mode 100644 index 00000000000..1eff9b85a50 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IOutgoingSendContext.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.Pipeline +{ + /// + /// Pipeline context for send operations. + /// + public interface IOutgoingSendContext : IOutgoingContext + { + /// + /// The message being sent. + /// + OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/IRoutingContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/IRoutingContext.cs new file mode 100644 index 00000000000..58ec22cf448 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/IRoutingContext.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.Pipeline +{ + using System.Collections.Generic; + using Routing; + using Transport; + + /// + /// Context for the routing part of the pipeline. + /// + public interface IRoutingContext : IBehaviorContext + { + /// + /// The message to dispatch the the transport. + /// + OutgoingMessage Message { get; } + + /// + /// The routing strategies for the operation to be dispatched. + /// + IReadOnlyCollection RoutingStrategies { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchOptionExtensions.cs b/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchOptionExtensions.cs new file mode 100644 index 00000000000..8d894b367a9 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchOptionExtensions.cs @@ -0,0 +1,41 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Provides ways for the end user to request immediate dispatch of their messages. + /// + public static class ImmediateDispatchOptionExtensions + { + /// + /// Requests the messsage to be dispatched to the transport immediately. + /// This means that the message is ACKed by the transport as soon as the call to send returns. + /// The message will not be enlisted in any current receive transaction even if the transport support it. + /// + /// The options being extended. + public static void RequireImmediateDispatch(this ExtendableOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.GetExtensions().Set(new RoutingToDispatchConnector.State + { + ImmediateDispatch = true + }); + } + + /// + /// Returns whether immediate dispatch has been request by or not. + /// + /// The options being extended. + /// True if immediate dispatch was requested, False otherwise. + public static bool RequiredImmediateDispatch(this ExtendableOptions options) + { + Guard.AgainstNull(nameof(options), options); + + RoutingToDispatchConnector.State state; + options.GetExtensions().TryGet(out state); + + return state?.ImmediateDispatch ?? false; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchTerminator.cs b/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchTerminator.cs new file mode 100644 index 00000000000..715869db8e1 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/ImmediateDispatchTerminator.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System.Linq; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class ImmediateDispatchTerminator : PipelineTerminator + { + public ImmediateDispatchTerminator(IDispatchMessages dispatcher) + { + this.dispatcher = dispatcher; + } + + protected override Task Terminate(IDispatchContext context) + { + var transaction = context.Extensions.GetOrCreate(); + return dispatcher.Dispatch(new TransportOperations(context.Operations.ToArray()), transaction, context.Extensions); + } + + IDispatchMessages dispatcher; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/MessageIdExtensions.cs b/src/NServiceBus.Core/Pipeline/Outgoing/MessageIdExtensions.cs new file mode 100644 index 00000000000..d8fcc9c3e15 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/MessageIdExtensions.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Extensions to the outgoing pipeline. + /// + public static class MessageIdExtensions + { + /// + /// Allows the user to set the message id. + /// + /// Options to extend. + /// The message id to use. + public static void SetMessageId(this ExtendableOptions options, string messageId) + { + Guard.AgainstNullAndEmpty(messageId, messageId); + + options.MessageId = messageId; + } + + /// + /// Returns the message id. + /// + /// Options to extend. + /// The message id. + public static string GetMessageId(this ExtendableOptions options) + { + return options.MessageId; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingContext.cs new file mode 100644 index 00000000000..39f3a81e449 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingContext.cs @@ -0,0 +1,51 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Pipeline; + + abstract class OutgoingContext : BehaviorContext, IOutgoingContext + { + protected OutgoingContext(string messageId, Dictionary headers, IBehaviorContext parentContext) + : base(parentContext) + { + Headers = headers; + MessageId = messageId; + } + + public string MessageId { get; } + + public Dictionary Headers { get; } + + public Task Send(object message, SendOptions options) + { + return MessageOperations.Send(this, message, options); + } + + public Task Send(Action messageConstructor, SendOptions options) + { + return MessageOperations.Send(this, messageConstructor, options); + } + + public Task Publish(object message, PublishOptions options) + { + return MessageOperations.Publish(this, message, options); + } + + public Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + return MessageOperations.Publish(this, messageConstructor, publishOptions); + } + + public Task Subscribe(Type eventType, SubscribeOptions options) + { + return MessageOperations.Subscribe(this, eventType, options); + } + + public Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + return MessageOperations.Unsubscribe(this, eventType, options); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessage.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessage.cs new file mode 100644 index 00000000000..ee363aceba3 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessage.cs @@ -0,0 +1,33 @@ +namespace NServiceBus.Pipeline +{ + using System; + + /// + /// Represents a logical message about to be push out to the transport. + /// + public class OutgoingLogicalMessage + { + /// + /// Initializes the message with a explicit message type and instance. Use this constructor if the message type is + /// different from the instance type. + /// + public OutgoingLogicalMessage(Type messageType, object message) + { + Guard.AgainstNull(nameof(messageType), messageType); + Guard.AgainstNull(nameof(message), message); + + MessageType = messageType; + Instance = message; + } + + /// + /// The of the message instance. + /// + public Type MessageType { get; private set; } + + /// + /// The message instance. + /// + public object Instance { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessageContext.cs new file mode 100644 index 00000000000..49954fb3b1c --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingLogicalMessageContext.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + using Routing; + + class OutgoingLogicalMessageContext : OutgoingContext, IOutgoingLogicalMessageContext + { + public OutgoingLogicalMessageContext(string messageId, Dictionary headers, OutgoingLogicalMessage message, IReadOnlyCollection routingStrategies, IBehaviorContext parentContext) + : base(messageId, headers, parentContext) + { + Message = message; + RoutingStrategies = routingStrategies; + Set(message); + } + + public OutgoingLogicalMessage Message { get; private set; } + + public IReadOnlyCollection RoutingStrategies { get; } + + public void UpdateMessage(object newInstance) + { + Guard.AgainstNull(nameof(newInstance), newInstance); + + Message = new OutgoingLogicalMessage(newInstance.GetType(), newInstance); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalMessageContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalMessageContext.cs new file mode 100644 index 00000000000..b3a24456234 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalMessageContext.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + using Routing; + + class OutgoingPhysicalMessageContext : OutgoingContext, IOutgoingPhysicalMessageContext + { + public OutgoingPhysicalMessageContext(byte[] body, IReadOnlyCollection routingStrategies, IOutgoingLogicalMessageContext parentContext) + : base(parentContext.MessageId, parentContext.Headers, parentContext) + { + Body = body; + RoutingStrategies = routingStrategies; + } + + public byte[] Body { get; private set; } + + public IReadOnlyCollection RoutingStrategies { get; } + + public void UpdateMessage(byte[] body) + { + Body = body; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalToRoutingConnector.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalToRoutingConnector.cs new file mode 100644 index 00000000000..d8630552348 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPhysicalToRoutingConnector.cs @@ -0,0 +1,17 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class OutgoingPhysicalToRoutingConnector : StageConnector + { + public override Task Invoke(IOutgoingPhysicalMessageContext context, Func stage) + { + var message = new OutgoingMessage(context.MessageId, context.Headers, context.Body); + + return stage(this.CreateRoutingContext(message, context.RoutingStrategies, context)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPipelineFeature.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPipelineFeature.cs new file mode 100644 index 00000000000..9f6bf476ce8 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPipelineFeature.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.Features +{ + using Transport; + + class OutgoingPipelineFeature : Feature + { + public OutgoingPipelineFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Pipeline.Register("MutateOutgoingMessages", new MutateOutgoingMessageBehavior(), "Executes IMutateOutgoingMessages"); + context.Pipeline.Register("MutateOutgoingTransportMessage", new MutateOutgoingTransportMessageBehavior(), "Executes IMutateOutgoingTransportMessages"); + + context.Pipeline.Register(new AttachSenderRelatedInfoOnMessageBehavior(), "Makes sure that outgoing messages contains relevant info on the sending endpoint."); + + context.Pipeline.Register(new ForceImmediateDispatchForOperationsInSuppressedScopeBehavior(), "Detects operations performed in a suppressed scope and request them to be immediately dispatched to the transport."); + + context.Pipeline.Register(new OutgoingPhysicalToRoutingConnector(), "Starts the message dispatch pipeline"); + context.Pipeline.Register(new RoutingToDispatchConnector(), "Decides if the current message should be batched or immediately be dispatched to the transport"); + context.Pipeline.Register(new BatchToDispatchConnector(), "Passes batched messages over to the immediate dispatch part of the pipeline"); + context.Pipeline.Register(b => new ImmediateDispatchTerminator(b.Build()), "Hands the outgoing messages over to the transport for immediate delivery"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPublishContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPublishContext.cs new file mode 100644 index 00000000000..80530149e05 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingPublishContext.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + + class OutgoingPublishContext : OutgoingContext, IOutgoingPublishContext + { + public OutgoingPublishContext(OutgoingLogicalMessage message, PublishOptions options, IBehaviorContext parentContext) + : base(options.MessageId, new Dictionary(options.OutgoingHeaders), parentContext) + { + Message = message; + Guard.AgainstNull(nameof(parentContext), parentContext); + Guard.AgainstNull(nameof(message), message); + Guard.AgainstNull(nameof(options), options); + + parentContext.Extensions.Merge(options.Context); + } + + public OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingReplyContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingReplyContext.cs new file mode 100644 index 00000000000..618abe53d35 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingReplyContext.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + + class OutgoingReplyContext : OutgoingContext, IOutgoingReplyContext + { + public OutgoingReplyContext(OutgoingLogicalMessage message, ReplyOptions options, IBehaviorContext parentContext) + : base(options.MessageId, new Dictionary(options.OutgoingHeaders), parentContext) + { + Message = message; + Guard.AgainstNull(nameof(parentContext), parentContext); + Guard.AgainstNull(nameof(message), message); + Guard.AgainstNull(nameof(options), options); + + parentContext.Extensions.Merge(options.Context); + } + + public OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingSendContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingSendContext.cs new file mode 100644 index 00000000000..b71e97209e9 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/OutgoingSendContext.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + + class OutgoingSendContext : OutgoingContext, IOutgoingSendContext + { + public OutgoingSendContext(OutgoingLogicalMessage message, SendOptions options, IBehaviorContext parentContext) + : base(options.MessageId, new Dictionary(options.OutgoingHeaders), parentContext) + { + Guard.AgainstNull(nameof(parentContext), parentContext); + Guard.AgainstNull(nameof(message), message); + Guard.AgainstNull(nameof(options), options); + + Message = message; + + Merge(options.Context); + } + + public OutgoingLogicalMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/RoutingContext.cs b/src/NServiceBus.Core/Pipeline/Outgoing/RoutingContext.cs new file mode 100644 index 00000000000..0bf77ef6123 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/RoutingContext.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Pipeline; + using Routing; + using Transport; + + class RoutingContext : OutgoingContext, IRoutingContext + { + public RoutingContext(OutgoingMessage messageToDispatch, RoutingStrategy routingStrategy, IBehaviorContext parentContext) + : this(messageToDispatch, new[] + { + routingStrategy + }, parentContext) + { + } + + public RoutingContext(OutgoingMessage messageToDispatch, IReadOnlyCollection routingStrategies, IBehaviorContext parentContext) + : base(messageToDispatch.MessageId, messageToDispatch.Headers, parentContext) + { + Message = messageToDispatch; + RoutingStrategies = routingStrategies; + } + + public OutgoingMessage Message { get; } + + public IReadOnlyCollection RoutingStrategies { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/RoutingToDispatchConnector.cs b/src/NServiceBus.Core/Pipeline/Outgoing/RoutingToDispatchConnector.cs new file mode 100644 index 00000000000..8ecc0ec4518 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/RoutingToDispatchConnector.cs @@ -0,0 +1,62 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using DeliveryConstraints; + using Logging; + using Pipeline; + using Routing; + using Transport; + + class RoutingToDispatchConnector : StageConnector + { + public override Task Invoke(IRoutingContext context, Func stage) + { + var state = context.Extensions.GetOrCreate(); + var dispatchConsistency = state.ImmediateDispatch ? DispatchConsistency.Isolated : DispatchConsistency.Default; + + var operations = context.RoutingStrategies + .Select(rs => + { + var addressLabel = rs.Apply(context.Message.Headers); + var message = new OutgoingMessage(context.Message.MessageId, context.Message.Headers, context.Message.Body); + return new TransportOperation(message, addressLabel, dispatchConsistency, context.Extensions.GetDeliveryConstraints()); + }).ToArray(); + + if (log.IsDebugEnabled) + { + var sb = new StringBuilder(); + foreach (var operation in operations) + { + var unicastAddressTag = operation.AddressTag as UnicastAddressTag; + if (unicastAddressTag != null) + { + sb.AppendFormat("Destination: {0}\n", unicastAddressTag.Destination); + } + + sb.AppendFormat("Message headers:\n{0}", string.Join(", ", operation.Message.Headers.Select(h => h.Key + ":" + h.Value).ToArray())); + log.Debug(sb.ToString()); + } + } + + PendingTransportOperations pendingOperations; + + if (!state.ImmediateDispatch && context.Extensions.TryGet(out pendingOperations)) + { + pendingOperations.AddRange(operations); + return TaskEx.CompletedTask; + } + + return stage(this.CreateDispatchContext(operations, context)); + } + + static ILog log = LogManager.GetLogger(); + + public class State + { + public bool ImmediateDispatch { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/SerializeMessageConnector.cs b/src/NServiceBus.Core/Pipeline/Outgoing/SerializeMessageConnector.cs new file mode 100644 index 00000000000..3de4c521513 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/SerializeMessageConnector.cs @@ -0,0 +1,69 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using Logging; + using Pipeline; + using Serialization; + using Unicast.Messages; + + class SerializeMessageConnector : StageConnector + { + public SerializeMessageConnector(IMessageSerializer messageSerializer, MessageMetadataRegistry messageMetadataRegistry) + { + this.messageSerializer = messageSerializer; + this.messageMetadataRegistry = messageMetadataRegistry; + } + + public override async Task Invoke(IOutgoingLogicalMessageContext context, Func stage) + { + if (log.IsDebugEnabled) + { + log.DebugFormat("Serializing message '{0}' with id '{1}', ToString() of the message yields: {2} \n", + context.Message.MessageType != null ? context.Message.MessageType.AssemblyQualifiedName : "unknown", + context.MessageId, context.Message.Instance); + } + + if (context.ShouldSkipSerialization()) + { + await stage(this.CreateOutgoingPhysicalMessageContext(new byte[0], context.RoutingStrategies, context)).ConfigureAwait(false); + return; + } + + context.Headers[Headers.ContentType] = messageSerializer.ContentType; + context.Headers[Headers.EnclosedMessageTypes] = SerializeEnclosedMessageTypes(context.Message.MessageType); + + var array = Serialize(context); + await stage(this.CreateOutgoingPhysicalMessageContext(array, context.RoutingStrategies, context)).ConfigureAwait(false); + } + + byte[] Serialize(IOutgoingLogicalMessageContext context) + { + using (var ms = new MemoryStream()) + { + messageSerializer.Serialize(context.Message.Instance, ms); + return ms.ToArray(); + } + } + + string SerializeEnclosedMessageTypes(Type messageType) + { + var metadata = messageMetadataRegistry.GetMessageMetadata(messageType); + + var assemblyQualifiedNames = new HashSet(); + foreach (var type in metadata.MessageHierarchy) + { + assemblyQualifiedNames.Add(type.AssemblyQualifiedName); + } + + return string.Join(";", assemblyQualifiedNames); + } + + MessageMetadataRegistry messageMetadataRegistry; + IMessageSerializer messageSerializer; + + static ILog log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Outgoing/TransportMessageContextExtensions.cs b/src/NServiceBus.Core/Pipeline/Outgoing/TransportMessageContextExtensions.cs new file mode 100644 index 00000000000..54c0448821b --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Outgoing/TransportMessageContextExtensions.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.Pipeline +{ + using Transport; + + /// + /// Context extension to provide access to the incoming physical message. + /// + public static class TransportMessageContextExtensions + { + /// + /// Returns the incoming physical message if there is one currently processed. + /// + public static bool TryGetIncomingPhysicalMessage(this IOutgoingReplyContext context, out IncomingMessage message) + { + Guard.AgainstNull(nameof(context), context); + + return context.Extensions.TryGet(out message); + } + + /// + /// Returns the incoming physical message if there is one currently processed. + /// + public static bool TryGetIncomingPhysicalMessage(this IOutgoingLogicalMessageContext context, out IncomingMessage message) + { + Guard.AgainstNull(nameof(context), context); + + return context.Extensions.TryGet(out message); + } + + /// + /// Returns the incoming physical message if there is one currently processed. + /// + public static bool TryGetIncomingPhysicalMessage(this IOutgoingPhysicalMessageContext context, out IncomingMessage message) + { + Guard.AgainstNull(nameof(context), context); + + return context.Extensions.TryGet(out message); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Pipeline.cs b/src/NServiceBus.Core/Pipeline/Pipeline.cs new file mode 100644 index 00000000000..c75319278e9 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Pipeline.cs @@ -0,0 +1,40 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using Janitor; + using ObjectBuilder; + using Pipeline; + using Settings; + + [SkipWeaving] + class Pipeline : IPipeline + where TContext : IBehaviorContext + { + public Pipeline(IBuilder builder, ReadOnlySettings settings, PipelineModifications pipelineModifications) + { + var coordinator = new StepRegistrationsCoordinator(pipelineModifications.Removals, pipelineModifications.Replacements); + + foreach (var rego in pipelineModifications.Additions.Where(x => x.IsEnabled(settings))) + { + coordinator.Register(rego); + } + + // Important to keep a reference + behaviors = coordinator.BuildPipelineModelFor() + .Select(r => r.CreateBehavior(builder)).ToArray(); + + pipeline = behaviors.CreatePipelineExecutionFuncFor(); + } + + public Task Invoke(TContext context) + { + return pipeline(context); + } + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + IBehavior[] behaviors; + Func pipeline; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineBuilder.cs b/src/NServiceBus.Core/Pipeline/PipelineBuilder.cs deleted file mode 100644 index b6027f27ee2..00000000000 --- a/src/NServiceBus.Core/Pipeline/PipelineBuilder.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System.Collections.Generic; - using Contexts; - using Logging; - - class PipelineBuilder - { - public PipelineBuilder(PipelineModifications modifications) - { - coordinator = new StepRegistrationsCoordinator(modifications.Removals, modifications.Replacements); - - RegisterIncomingCoreBehaviors(); - RegisterOutgoingCoreBehaviors(); - RegisterAdditionalBehaviors(modifications.Additions); - - var model = coordinator.BuildRuntimeModel(); - Incoming = new List(); - Outgoing = new List(); - var behaviorType = typeof(IBehavior<>); - var outgoingContextType = typeof(OutgoingContext); - var incomingContextType = typeof(IncomingContext); - - foreach (var rego in model) - { - if (behaviorType.MakeGenericType(incomingContextType).IsAssignableFrom(rego.BehaviorType)) - { - Incoming.Add(rego); - } - - if (behaviorType.MakeGenericType(outgoingContextType).IsAssignableFrom(rego.BehaviorType)) - { - Outgoing.Add(rego); - } - } - } - - public List Incoming { get; private set; } - public List Outgoing { get; private set; } - - void RegisterAdditionalBehaviors(List additions) - { - foreach (var rego in additions) - { - coordinator.Register(rego); - } - } - - void RegisterIncomingCoreBehaviors() - { - coordinator.Register(WellKnownStep.ProcessingStatistics, typeof(ProcessingStatisticsBehavior), "Add ProcessingStarted and ProcessingEnded headers"); - coordinator.Register(WellKnownStep.CreateChildContainer, typeof(ChildContainerBehavior), "Creates the child container"); - coordinator.Register(WellKnownStep.ExecuteUnitOfWork, typeof(UnitOfWorkBehavior), "Executes the UoW"); - coordinator.Register("ProcessSubscriptionRequests", typeof(SubscriptionReceiverBehavior), "Check for subscription messages and execute the requested behavior to subscribe or unsubscribe."); - coordinator.Register(WellKnownStep.MutateIncomingTransportMessage, typeof(ApplyIncomingTransportMessageMutatorsBehavior), "Executes IMutateIncomingTransportMessages"); - coordinator.Register(WellKnownStep.DeserializeMessages, typeof(DeserializeLogicalMessagesBehavior), "Deserializes the physical message body into logical messages"); - coordinator.Register("InvokeRegisteredCallbacks", typeof(CallbackInvocationBehavior), "Updates the callback inmemory dictionary"); - coordinator.Register(WellKnownStep.ExecuteLogicalMessages, typeof(ExecuteLogicalMessagesBehavior), "Starts the execution of each logical message"); - coordinator.Register(WellKnownStep.MutateIncomingMessages, typeof(ApplyIncomingMessageMutatorsBehavior), "Executes IMutateIncomingMessages"); - coordinator.Register(WellKnownStep.LoadHandlers, typeof(LoadHandlersBehavior), "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - coordinator.Register("SetCurrentMessageBeingHandled", typeof(SetCurrentMessageBeingHandledBehavior), "Sets the static current message (this is used by the headers)"); - coordinator.Register(WellKnownStep.InvokeHandlers, typeof(InvokeHandlersBehavior), "Calls the IHandleMessages.Handle(T)"); - } - - void RegisterOutgoingCoreBehaviors() - { - coordinator.Register(WellKnownStep.EnforceBestPractices, typeof(SendValidatorBehavior), "Enforces messaging best practices"); - coordinator.Register(WellKnownStep.MutateOutgoingMessages, typeof(MutateOutgoingMessageBehavior), "Executes IMutateOutgoingMessages"); - coordinator.Register("PopulateAutoCorrelationHeadersForReplies", typeof(PopulateAutoCorrelationHeadersForRepliesBehavior), "Copies existing saga headers from incoming message to outgoing message to facilitate the auto correlation in the saga, when replying to a message that was sent by a saga."); - coordinator.Register(WellKnownStep.CreatePhysicalMessage, typeof(CreatePhysicalMessageBehavior), "Converts a logical message into a physical message"); - coordinator.Register("FixSendIntent", typeof(FixSendIntentBehavior), "Fixes an issue where ReplyToOriginator isn't able to set the Reply message intent, breaking callbacks"); - coordinator.Register(WellKnownStep.SerializeMessage, typeof(SerializeMessagesBehavior), "Serializes the message to be sent out on the wire"); - coordinator.Register(WellKnownStep.MutateOutgoingTransportMessage, typeof(MutateOutgoingPhysicalMessageBehavior), "Executes IMutateOutgoingTransportMessages"); - if (LogManager.GetLogger("LogOutgoingMessage").IsDebugEnabled) - { - coordinator.Register("LogOutgoingMessage", typeof(LogOutgoingMessageBehavior), "Logs the message contents before it is sent."); - } - coordinator.Register(WellKnownStep.DispatchMessageToTransport, typeof(DispatchMessageToTransportBehavior), "Dispatches messages to the transport"); - } - - StepRegistrationsCoordinator coordinator; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineCache.cs b/src/NServiceBus.Core/Pipeline/PipelineCache.cs new file mode 100644 index 00000000000..eacf6d794da --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineCache.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading; + using ObjectBuilder; + using Pipeline; + using Settings; + + class PipelineCache : IPipelineCache + { + public PipelineCache(IBuilder builder, ReadOnlySettings settings) + { + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + FromMainPipeline(builder, settings); + } + + public IPipeline Pipeline() + where TContext : IBehaviorContext + + { + Lazy lazyPipeline; + if (pipelines.TryGetValue(typeof(TContext), out lazyPipeline)) + { + return (IPipeline) lazyPipeline.Value; + } + return default(IPipeline); + } + + void FromMainPipeline(IBuilder builder, ReadOnlySettings settings) + where TContext : IBehaviorContext + { + var lazyPipeline = new Lazy(() => + { + var pipelinesCollection = settings.Get(); + var pipeline = new Pipeline(builder, settings, pipelinesCollection.Modifications); + return pipeline; + }, LazyThreadSafetyMode.ExecutionAndPublication); + pipelines.Add(typeof(TContext), lazyPipeline); + } + + Dictionary> pipelines = new Dictionary>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineConfiguration.cs b/src/NServiceBus.Core/Pipeline/PipelineConfiguration.cs new file mode 100644 index 00000000000..cf7944bef1a --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineConfiguration.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using ObjectBuilder; + using Settings; + + class PipelineConfiguration + { + public void RegisterBehaviorsInContainer(SettingsHolder settings, IConfigureComponents container) + { + foreach (var registeredBehavior in Modifications.Replacements) + { + container.ConfigureComponent(registeredBehavior.BehaviorType, DependencyLifecycle.InstancePerCall); + } + + foreach (var step in Modifications.Additions) + { + step.ApplyContainerRegistration(settings, container); + } + } + + public PipelineModifications Modifications = new PipelineModifications(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineExecutionExtensions.cs b/src/NServiceBus.Core/Pipeline/PipelineExecutionExtensions.cs new file mode 100644 index 00000000000..8fb3794baed --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineExecutionExtensions.cs @@ -0,0 +1,92 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Threading.Tasks; + using Pipeline; + + static class PipelineExecutionExtensions + { + // ReSharper disable once SuggestBaseTypeForParameter + public static Func CreatePipelineExecutionFuncFor(this IBehavior[] behaviors) + where TRootContext : IBehaviorContext + { + return (Func)behaviors.CreatePipelineExecutionExpression(); + } + + /// + /// rootContext + /// => behavior1.Invoke(rootContext, + /// context1 => behavior2.Invoke(context1, + /// ... + /// context{N} => behavior{N}.Invoke(context{N}, + /// context{N+1} => TaskEx.Completed)) + /// + public static Delegate CreatePipelineExecutionExpression(this IBehavior[] behaviors, List expressions = null) + { + Delegate lambdaExpression = null; + var behaviorCount = behaviors.Length - 1; + // We start from the end of the list know the lambda expressions deeper in the call stack in advance + for (var i = behaviorCount; i >= 0; i--) + { + var currentBehavior = behaviors[i]; + var behaviorInterfaceType = currentBehavior.GetType().GetBehaviorInterface(); + if (behaviorInterfaceType == null) + { + throw new InvalidOperationException("Behaviors must implement IBehavior"); + } + // Select the method on the type which was implemented from the behavior interface. + var methodInfo = currentBehavior.GetType().GetInterfaceMap(behaviorInterfaceType).TargetMethods.FirstOrDefault(); + if (methodInfo == null) + { + throw new InvalidOperationException("Behaviors must implement IBehavior and provide an invocation method."); + } + + var genericArguments = behaviorInterfaceType.GetGenericArguments(); + var inContextType = genericArguments[0]; + + var inContextParameter = Expression.Parameter(inContextType, $"context{i}"); + + if (i == behaviorCount) + { + if (currentBehavior is IPipelineTerminator) + { + inContextType = typeof(PipelineTerminator<>.ITerminatingContext).MakeGenericType(inContextType); + } + var doneDelegate = CreateDoneDelegate(inContextType, i); + lambdaExpression = CreateBehaviorCallDelegate(currentBehavior, methodInfo, inContextParameter, doneDelegate, expressions); + continue; + } + + lambdaExpression = CreateBehaviorCallDelegate(currentBehavior, methodInfo, inContextParameter, lambdaExpression, expressions); + } + + return lambdaExpression; + } + + // ReSharper disable once SuggestBaseTypeForParameter + + /// + /// context{i} => behavior.Invoke(context{i}, context{i+1} => previous) + /// > + static Delegate CreateBehaviorCallDelegate(IBehavior currentBehavior, MethodInfo methodInfo, ParameterExpression outerContextParam, Delegate previous, List expressions = null) + { + Expression body = Expression.Call(Expression.Constant(currentBehavior), methodInfo, outerContextParam, Expression.Constant(previous)); + var lambdaExpression = Expression.Lambda(body, outerContextParam); + expressions?.Add(lambdaExpression); + return lambdaExpression.Compile(); + } + + /// + /// context{i} => return TaskEx.CompletedTask; + /// > + static Delegate CreateDoneDelegate(Type inContextType, int i) + { + var innerContextParam = Expression.Parameter(inContextType, $"context{i + 1}"); + return Expression.Lambda(typeof(Func<,>).MakeGenericType(inContextType, typeof(Task)), Expression.Constant(TaskEx.CompletedTask), innerContextParam).Compile(); + } + } +} diff --git a/src/NServiceBus.Core/Pipeline/PipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/PipelineExecutor.cs deleted file mode 100644 index f51052d4179..00000000000 --- a/src/NServiceBus.Core/Pipeline/PipelineExecutor.cs +++ /dev/null @@ -1,143 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Contexts; - using ObjectBuilder; - using Settings; - using Unicast; - using Unicast.Messages; - - /// - /// Orchestrates the execution of a pipeline. - /// - public class PipelineExecutor : IDisposable - { - /// - /// Create a new instance of . - /// - /// The settings to read data from. - /// The builder. - /// Bus notifications. - public PipelineExecutor(ReadOnlySettings settings, IBuilder builder, BusNotifications busNotifications) - { - rootBuilder = builder; - this.busNotifications = busNotifications; - - var pipelineBuilder = new PipelineBuilder(settings.Get()); - Incoming = pipelineBuilder.Incoming.AsReadOnly(); - Outgoing = pipelineBuilder.Outgoing.AsReadOnly(); - - incomingBehaviors = Incoming.Select(r => r.BehaviorType); - outgoingBehaviors = Outgoing.Select(r => r.BehaviorType); - } - - /// - /// The list of incoming steps registered. - /// - public IList Incoming { get; private set; } - - /// - /// The list of outgoing steps registered. - /// - public IList Outgoing { get; private set; } - - /// - /// The current context being executed. - /// - public BehaviorContext CurrentContext - { - get - { - var current = contextStacker.Current; - - if (current != null) - { - return current; - } - - contextStacker.Push(new RootContext(rootBuilder)); - - return contextStacker.Current; - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - //Injected - } - - /// - /// Invokes a chain of behaviors. - /// - /// The context to use. - /// The behaviors to execute in the specified order. - /// The context instance. - public void InvokePipeline(IEnumerable behaviors, TContext context) where TContext : BehaviorContext - { - var pipeline = new BehaviorChain(behaviors, context, this, busNotifications); - - Execute(pipeline, context); - } - - internal void PreparePhysicalMessagePipelineContext(TransportMessage message) - { - contextStacker.Push(new IncomingContext(CurrentContext, message)); - } - - internal void InvokeReceivePhysicalMessagePipeline() - { - var context = contextStacker.Current as IncomingContext; - - if (context == null) - { - throw new InvalidOperationException("Can't invoke the receive pipeline when the current context is: " + contextStacker.Current.GetType().Name); - } - - InvokePipeline(incomingBehaviors, context); - } - - internal void CompletePhysicalMessagePipelineContext() - { - contextStacker.Pop(); - } - - internal OutgoingContext InvokeSendPipeline(DeliveryOptions deliveryOptions, LogicalMessage message) - { - var context = new OutgoingContext(CurrentContext, deliveryOptions, message); - - InvokePipeline(outgoingBehaviors, context); - - return context; - } - - void DisposeManaged() - { - contextStacker.Dispose(); - } - - void Execute(BehaviorChain pipelineAction, T context) where T : BehaviorContext - { - try - { - contextStacker.Push(context); - pipelineAction.Invoke(); - } - finally - { - contextStacker.Pop(); - } - } - - BehaviorContextStacker contextStacker = new BehaviorContextStacker(); - IEnumerable incomingBehaviors; - IEnumerable outgoingBehaviors; - IBuilder rootBuilder; - readonly BusNotifications busNotifications; - } -} diff --git a/src/NServiceBus.Core/Pipeline/PipelineModelBuilder.cs b/src/NServiceBus.Core/Pipeline/PipelineModelBuilder.cs new file mode 100644 index 00000000000..2a0ca83bd03 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineModelBuilder.cs @@ -0,0 +1,310 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Logging; + using Pipeline; + + class PipelineModelBuilder + { + public PipelineModelBuilder(Type rootContextType, List additions, List removals, List replacements) + { + this.rootContextType = rootContextType; + this.additions = additions; + this.removals = removals; + this.replacements = replacements; + } + + public List Build() + { + var registrations = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + var listOfBeforeAndAfterIds = new List(); + + // Let's do some validation too + + //Step 1: validate that additions are unique + foreach (var metadata in additions) + { + if (!registrations.ContainsKey(metadata.StepId)) + { + registrations.Add(metadata.StepId, metadata); + if (metadata.Afters != null) + { + listOfBeforeAndAfterIds.AddRange(metadata.Afters.Select(a => a.DependsOnId)); + } + if (metadata.Befores != null) + { + listOfBeforeAndAfterIds.AddRange(metadata.Befores.Select(b => b.DependsOnId)); + } + + continue; + } + + var message = $"Step registration with id '{metadata.StepId}' is already registered for '{registrations[metadata.StepId].BehaviorType}'."; + throw new Exception(message); + } + + // Step 2: do replacements + foreach (var metadata in replacements) + { + if (!registrations.ContainsKey(metadata.ReplaceId)) + { + var message = $"You can only replace an existing step registration, '{metadata.ReplaceId}' registration does not exist."; + throw new Exception(message); + } + + var registerStep = registrations[metadata.ReplaceId]; + registerStep.Replace(metadata); + } + + // Step 3: validate the removals + foreach (var metadata in removals.Distinct(idComparer)) + { + if (!registrations.ContainsKey(metadata.RemoveId)) + { + var message = $"You cannot remove step registration with id '{metadata.RemoveId}', registration does not exist."; + throw new Exception(message); + } + + if (listOfBeforeAndAfterIds.Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase)) + { + var add = additions.First(mr => (mr.Befores != null && mr.Befores.Select(b => b.DependsOnId).Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase)) || + (mr.Afters != null && mr.Afters.Select(b => b.DependsOnId).Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase))); + + var message = $"You cannot remove step registration with id '{metadata.RemoveId}', registration with id '{add.StepId}' depends on it."; + throw new Exception(message); + } + + registrations.Remove(metadata.RemoveId); + } + + var stages = registrations.Values.GroupBy(r => r.GetInputContext()) + .ToList(); + + var finalOrder = new List(); + + if (registrations.Count == 0) + { + return finalOrder; + } + + var currentStage = stages.SingleOrDefault(stage => stage.Key == rootContextType); + + if (currentStage == null) + { + throw new Exception($"Can't find any behaviors/connectors for the root context ({rootContextType.FullName})"); + } + + var stageNumber = 1; + + while (currentStage != null) + { + var stageSteps = currentStage.Where(stageStep => !IsStageConnector(stageStep)).ToList(); + + //add the stage connector + finalOrder.AddRange(Sort(stageSteps)); + + var stageConnectors = currentStage.Where(IsStageConnector).ToList(); + + if (stageConnectors.Count > 1) + { + var connectors = $"'{string.Join("', '", stageConnectors.Select(sc => sc.BehaviorType.FullName))}'"; + throw new Exception($"Multiple stage connectors found for stage '{currentStage.Key.FullName}'. Remove one of: {connectors}"); + } + + var stageConnector = stageConnectors.FirstOrDefault(); + + if (stageConnector == null) + { + if (stageNumber < stages.Count) + { + throw new Exception($"No stage connector found for stage {currentStage.Key.FullName}"); + } + + currentStage = null; + } + else + { + finalOrder.Add(stageConnector); + + if (typeof(IPipelineTerminator).IsAssignableFrom(stageConnector.BehaviorType)) + { + currentStage = null; + } + else + { + var stageEndType = stageConnector.GetOutputContext(); + currentStage = stages.SingleOrDefault(stage => stage.Key == stageEndType); + } + } + + stageNumber++; + } + + return finalOrder; + } + + static bool IsStageConnector(RegisterStep stageStep) + { + return typeof(IStageConnector).IsAssignableFrom(stageStep.BehaviorType); + } + + static IEnumerable Sort(List registrations) + { + if (registrations.Count == 0) + { + return registrations; + } + + // Step 1: create nodes for graph + var nameToNode = new Dictionary(); + var allNodes = new List(); + foreach (var rego in registrations) + { + // create entries to preserve order within + var node = new Node(rego); + nameToNode[rego.StepId] = node; + allNodes.Add(node); + } + + // Step 2: create edges from InsertBefore/InsertAfter values + foreach (var node in allNodes) + { + ProcessBefores(node, nameToNode); + ProcessAfters(node, nameToNode); + } + + // Step 3: Perform Topological Sort + var output = new List(); + foreach (var node in allNodes) + { + node.Visit(output); + } + + return output; + } + + static void ProcessBefores(Node node, Dictionary nameToNode) + { + if (node.Befores == null) + { + return; + } + foreach (var beforeReference in node.Befores) + { + Node referencedNode; + if (nameToNode.TryGetValue(beforeReference.DependsOnId, out referencedNode)) + { + referencedNode.previous.Add(node); + continue; + } + var currentStepIds = GetCurrentIds(nameToNode); + var message = $"Registration '{beforeReference.DependsOnId}' specified in the insertbefore of the '{node.StepId}' step does not exist. Current StepIds: {currentStepIds}"; + + if (!beforeReference.Enforce) + { + Logger.Debug(message); + } + else + { + throw new Exception(message); + } + } + } + + static void ProcessAfters(Node node, Dictionary nameToNode) + { + if (node.Afters == null) + { + return; + } + foreach (var afterReference in node.Afters) + { + Node referencedNode; + if (nameToNode.TryGetValue(afterReference.DependsOnId, out referencedNode)) + { + node.previous.Add(referencedNode); + continue; + } + var currentStepIds = GetCurrentIds(nameToNode); + var message = $"Registration '{afterReference.DependsOnId}' specified in the insertafter of the '{node.StepId}' step does not exist. Current StepIds: {currentStepIds}"; + + if (!afterReference.Enforce) + { + Logger.Debug(message); + } + else + { + throw new Exception(message); + } + } + } + + static string GetCurrentIds(Dictionary nameToNodeDict) + { + return $"'{string.Join("', '", nameToNodeDict.Keys)}'"; + } + + List additions; + List removals; + List replacements; + + Type rootContextType; + static CaseInsensitiveIdComparer idComparer = new CaseInsensitiveIdComparer(); + static ILog Logger = LogManager.GetLogger(); + + class Node + { + public Node(RegisterStep registerStep) + { + rego = registerStep; + Befores = registerStep.Befores; + Afters = registerStep.Afters; + StepId = registerStep.StepId; + + OutputContext = registerStep.GetOutputContext(); + } + + public Type OutputContext { get; private set; } + + internal void Visit(List output) + { + if (visited) + { + return; + } + visited = true; + foreach (var n in previous) + { + n.Visit(output); + } + if (rego != null) + { + output.Add(rego); + } + } + + public List Afters; + public List Befores; + + public string StepId; + internal List previous = new List(); + RegisterStep rego; + bool visited; + } + + class CaseInsensitiveIdComparer : IEqualityComparer + { + public bool Equals(RemoveStep x, RemoveStep y) + { + return x.RemoveId.Equals(y.RemoveId, StringComparison.CurrentCultureIgnoreCase); + } + + public int GetHashCode(RemoveStep obj) + { + return obj.RemoveId.ToLower().GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineModifications.cs b/src/NServiceBus.Core/Pipeline/PipelineModifications.cs index 3c764275d5e..f5c04a67fb2 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineModifications.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineModifications.cs @@ -1,11 +1,12 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { using System.Collections.Generic; + using Pipeline; class PipelineModifications { public List Additions = new List(); public List Removals = new List(); - public List Replacements = new List(); + public List Replacements = new List(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineNotifications.cs b/src/NServiceBus.Core/Pipeline/PipelineNotifications.cs deleted file mode 100644 index d6c154a9452..00000000000 --- a/src/NServiceBus.Core/Pipeline/PipelineNotifications.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - - /// - /// Pipeline notifications - /// - public class PipelineNotifications : IDisposable - { - /// - /// Notification when a message is dequeued. - /// - public IObservable> ReceiveStarted - { - get { return receiveStarted; } - } - - void IDisposable.Dispose() - { - //Injected - } - - internal void InvokeReceiveStarted(IObservable pipe) - { - receiveStarted.OnNext(pipe); - } - - Observable> receiveStarted = new Observable>(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineSettings.cs b/src/NServiceBus.Core/Pipeline/PipelineSettings.cs index 921659ffd67..b0ee2082cb1 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineSettings.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineSettings.cs @@ -1,18 +1,19 @@ namespace NServiceBus.Pipeline { using System; + using ObjectBuilder; /// /// Manages the pipeline configuration. /// - public class PipelineSettings + public partial class PipelineSettings { /// - /// Creates an instance of + /// Initializes a new instance of . /// - public PipelineSettings(BusConfiguration config) + internal PipelineSettings(PipelineModifications modifications) { - this.config = config; + this.modifications = modifications; } /// @@ -22,113 +23,161 @@ public PipelineSettings(BusConfiguration config) public void Remove(string stepId) { // I can only remove a behavior that is registered and other behaviors do not depend on, eg InsertBefore/After - if (string.IsNullOrEmpty(stepId)) - { - throw new ArgumentNullException("stepId"); - } + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); - config.Settings.Get().Removals.Add(new RemoveStep(stepId)); + modifications.Removals.Add(new RemoveStep(stepId)); } /// - /// Removes the specified step from the pipeline. + /// Replaces an existing step behavior with a new one. /// - /// The identifier of the well known step to remove. - public void Remove(WellKnownStep wellKnownStep) + /// The identifier of the step to replace its implementation. + /// The new to use. + /// The description of the new behavior. + public void Replace(string stepId, Type newBehavior, string description = null) { - // I can only remove a behavior that is registered and other behaviors do not depend on, eg InsertBefore/After - if (wellKnownStep == null) - { - throw new ArgumentNullException("wellKnownStep"); - } + BehaviorTypeChecker.ThrowIfInvalid(newBehavior, nameof(newBehavior)); + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); - Remove((string)wellKnownStep); + modifications.Replacements.Add(new ReplaceStep(stepId, newBehavior, description)); } /// /// Replaces an existing step behavior with a new one. /// /// The identifier of the step to replace its implementation. - /// The new to use. + /// The new to use. /// The description of the new behavior. - public void Replace(string stepId, Type newBehavior, string description = null) + public void Replace(string stepId, T newBehavior, string description = null) + where T : IBehavior { - BehaviorTypeChecker.ThrowIfInvalid(newBehavior, "newBehavior"); + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), nameof(newBehavior)); + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); - if (string.IsNullOrEmpty(stepId)) - { - throw new ArgumentNullException("stepId"); - } - - config.Settings.Get().Replacements.Add(new ReplaceBehavior(stepId, newBehavior, description)); + modifications.Replacements.Add(new ReplaceStep(stepId, typeof(T), description, builder => newBehavior)); } /// - /// + /// Replaces an existing step behavior with a new one. /// - /// The identifier of the well known step to replace. - /// The new to use. + /// The identifier of the step to replace its implementation. + /// The factory method to create new instances of the behavior. /// The description of the new behavior. - public void Replace(WellKnownStep wellKnownStep, Type newBehavior, string description = null) + public void Replace(string stepId, Func factoryMethod, string description = null) + where T : IBehavior + { + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), "newBehavior"); + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); + + modifications.Replacements.Add(new ReplaceStep(stepId, typeof(T), description, b => factoryMethod(b))); + } + + /// + /// Register a new step into the pipeline. + /// + /// The to execute. + /// The description of the behavior. + public void Register(Type behavior, string description) { - if (wellKnownStep == null) - { - throw new ArgumentNullException("wellKnownStep"); - } + BehaviorTypeChecker.ThrowIfInvalid(behavior, nameof(behavior)); - Replace((string)wellKnownStep, newBehavior, description); + Register(behavior.Name, behavior, description); } - + /// /// Register a new step into the pipeline. /// /// The identifier of the new step to add. - /// The to execute. + /// The to execute. /// The description of the behavior. public void Register(string stepId, Type behavior, string description) { - BehaviorTypeChecker.ThrowIfInvalid(behavior, "behavior"); + BehaviorTypeChecker.ThrowIfInvalid(behavior, nameof(behavior)); - if (string.IsNullOrEmpty(stepId)) - { - throw new ArgumentNullException("stepId"); - } + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); + Guard.AgainstNullAndEmpty(nameof(description), description); - if (string.IsNullOrEmpty(description)) - { - throw new ArgumentNullException("description"); - } + modifications.Additions.Add(RegisterStep.Create(stepId, behavior, description)); + } + + /// + /// Register a new step into the pipeline. + /// + /// A callback that creates the behavior instance. + /// The description of the behavior. + public void Register(Func factoryMethod, string description) + where T : IBehavior + { + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), "behavior"); - config.Settings.Get().Additions.Add(RegisterStep.Create(stepId, behavior, description)); + Register(typeof(T).Name, factoryMethod, description); } + /// + /// Register a new step into the pipeline. + /// + /// The identifier of the new step to add. + /// A callback that creates the behavior instance. + /// The description of the behavior. + public void Register(string stepId, Func factoryMethod, string description) + where T : IBehavior + { + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), "behavior"); + + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); + Guard.AgainstNullAndEmpty(nameof(description), description); + + modifications.Additions.Add(RegisterStep.Create(stepId, typeof(T), description, b => factoryMethod(b))); + } /// - /// + /// Register a new step into the pipeline. /// - /// The identifier of the step to add. - /// The to execute. + /// The behavior instance. /// The description of the behavior. - public void Register(WellKnownStep wellKnownStep, Type behavior, string description) + public void Register(T behavior, string description) + where T : IBehavior { - if (wellKnownStep == null) - { - throw new ArgumentNullException("wellKnownStep"); - } + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), nameof(behavior)); - Register((string)wellKnownStep, behavior, description); + Register(typeof(T).Name, behavior, description); } + /// + /// Register a new step into the pipeline. + /// + /// The identifier of the new step to add. + /// The behavior instance. + /// The description of the behavior. + public void Register(string stepId, T behavior, string description) + where T : IBehavior + { + BehaviorTypeChecker.ThrowIfInvalid(typeof(T), nameof(behavior)); + + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); + Guard.AgainstNullAndEmpty(nameof(description), description); + + modifications.Additions.Add(RegisterStep.Create(stepId, typeof(T), description, _ => behavior)); + } + + /// + /// Register a new step into the pipeline. + /// + public void Register() where TRegisterStep : RegisterStep, new() + { + modifications.Additions.Add(new TRegisterStep()); + } /// /// Register a new step into the pipeline. /// - /// The to use to perform the registration. - public void Register() where T : RegisterStep, new() + /// The step registration. + public void Register(RegisterStep registration) { - config.Settings.Get().Additions.Add(new T()); + Guard.AgainstNull(nameof(registration), registration); + modifications.Additions.Add(registration); } - BusConfiguration config; + PipelineModifications modifications; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineTerminator.cs b/src/NServiceBus.Core/Pipeline/PipelineTerminator.cs new file mode 100644 index 00000000000..c87ba599b7a --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineTerminator.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// Marks the inner most behavior of the pipeline. + /// + /// The pipeline context type to terminate. + public abstract class PipelineTerminator : StageConnector.ITerminatingContext>, IPipelineTerminator where T : IBehaviorContext + { + /// + /// This method will be the final one to be called before the pipeline starts to travers back up the "stack". + /// + /// The current context. + protected abstract Task Terminate(T context); + + /// + /// Invokes the terminate method. + /// + /// Context object. + /// Ignored since there by definition is no next behavior to call. + public sealed override Task Invoke(T context, Func next) + { + Guard.AgainstNull(nameof(next), next); + + return Terminate(context); + } + + /// + /// A wellknow context that terminates the pipeline. + /// + public interface ITerminatingContext : IBehaviorContext + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/RegisterStep.cs b/src/NServiceBus.Core/Pipeline/RegisterStep.cs index d02ca274cac..4ce916a8983 100644 --- a/src/NServiceBus.Core/Pipeline/RegisterStep.cs +++ b/src/NServiceBus.Core/Pipeline/RegisterStep.cs @@ -2,31 +2,29 @@ namespace NServiceBus.Pipeline { using System; using System.Collections.Generic; + using System.Diagnostics; + using ObjectBuilder; + using Settings; /// /// Base class to do an advance registration of a step. /// - public abstract class RegisterStep + [DebuggerDisplay("{StepId}({BehaviorType.FullName}) - {Description}")] + public abstract partial class RegisterStep { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The unique identifier for this steps. - /// The type of to register. + /// The type of to register. /// A brief description of what this step does. - protected RegisterStep(string stepId, Type behavior, string description) + /// A factory method for creating the behavior. + protected RegisterStep(string stepId, Type behavior, string description, Func factoryMethod = null) { + this.factoryMethod = factoryMethod; BehaviorTypeChecker.ThrowIfInvalid(behavior, "behavior"); - - if (String.IsNullOrEmpty(stepId)) - { - throw new ArgumentNullException("stepId"); - } - - if (String.IsNullOrEmpty(description)) - { - throw new ArgumentNullException("description"); - } + Guard.AgainstNullAndEmpty(nameof(stepId), stepId); + Guard.AgainstNullAndEmpty(nameof(description), description); BehaviorType = behavior; StepId = stepId; @@ -36,162 +34,139 @@ protected RegisterStep(string stepId, Type behavior, string description) /// /// Gets the unique identifier for this step. /// - public string StepId { get; private set; } - + public string StepId { get; } + /// /// Gets the description for this registration. /// - public string Description { get; internal set; } + public string Description { get; private set; } - internal IList Befores { get; private set; } - internal IList Afters { get; private set; } - - /// - /// Gets the type of that is being registered. - /// - public Type BehaviorType { get; internal set; } + internal List Befores { get; private set; } + internal List Afters { get; private set; } /// - /// Instructs the pipeline to register this step before the one. If the does not exist, this condition is ignored. + /// Gets the type of that is being registered. /// - /// The that we want to insert before. - public void InsertBeforeIfExists(WellKnownStep step) - { - if (step == null) - { - throw new ArgumentNullException("step"); - } - - InsertBeforeIfExists((string) step); - } + public Type BehaviorType { get; private set; } - /// - /// Instructs the pipeline to register this step before the one. If the does not exist, this condition is ignored. - /// - /// The unique identifier of the step that we want to insert before. - public void InsertBeforeIfExists(string id) + internal void ApplyContainerRegistration(ReadOnlySettings settings, IConfigureComponents container) { - if (String.IsNullOrEmpty(id)) + if (!IsEnabled(settings) || factoryMethod != null) { - throw new ArgumentNullException("id"); + return; } - if (Befores == null) - { - Befores = new List(); - } - - Befores.Add(new Dependency(id, false)); + container.ConfigureComponent(BehaviorType, DependencyLifecycle.InstancePerCall); } /// - /// Instructs the pipeline to register this step before the one. + /// Checks if this behavior is enabled. /// - public void InsertBefore(WellKnownStep step) + public virtual bool IsEnabled(ReadOnlySettings settings) { - if (step == null) - { - throw new ArgumentNullException("step"); - } - - InsertBefore((string)step); + return true; } /// - /// Instructs the pipeline to register this step before the one. + /// Instructs the pipeline to register this step before the one. If the does + /// not exist, this condition is ignored. /// - public void InsertBefore(string id) + /// The unique identifier of the step that we want to insert before. + public void InsertBeforeIfExists(string id) { - if (String.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } + Guard.AgainstNullAndEmpty(nameof(id), id); if (Befores == null) { Befores = new List(); } - Befores.Add(new Dependency(id, true)); + Befores.Add(new Dependency(StepId, id, Dependency.DependencyDirection.Before, false)); } /// - /// Instructs the pipeline to register this step after the one. If the does not exist, this condition is ignored. + /// Instructs the pipeline to register this step before the one. /// - /// The unique identifier of the step that we want to insert after. - public void InsertAfterIfExists(WellKnownStep step) + public void InsertBefore(string id) { - if (step == null) + Guard.AgainstNullAndEmpty(nameof(id), id); + + if (Befores == null) { - throw new ArgumentNullException("step"); + Befores = new List(); } - InsertAfterIfExists((string)step); + Befores.Add(new Dependency(StepId, id, Dependency.DependencyDirection.Before, true)); } /// - /// Instructs the pipeline to register this step after the one. If the does not exist, this condition is ignored. + /// Instructs the pipeline to register this step after the one. If the does + /// not exist, this condition is ignored. /// /// The unique identifier of the step that we want to insert after. public void InsertAfterIfExists(string id) { - if (String.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } + Guard.AgainstNullAndEmpty(nameof(id), id); if (Afters == null) { Afters = new List(); } - Afters.Add(new Dependency(id, false)); + Afters.Add(new Dependency(StepId, id, Dependency.DependencyDirection.After, false)); } /// - /// Instructs the pipeline to register this step after the one. + /// Instructs the pipeline to register this step after the one. /// - public void InsertAfter(WellKnownStep step) + public void InsertAfter(string id) { - if (step == null) + Guard.AgainstNullAndEmpty(nameof(id), id); + + if (Afters == null) { - throw new ArgumentNullException("step"); + Afters = new List(); } - InsertAfter((string)step); + Afters.Add(new Dependency(StepId, id, Dependency.DependencyDirection.After, true)); } - /// - /// Instructs the pipeline to register this step after the one. - /// - public void InsertAfter(string id) + internal void Replace(ReplaceStep replacement) { - if (String.IsNullOrEmpty(id)) + if (StepId != replacement.ReplaceId) { - throw new ArgumentNullException("id"); + throw new InvalidOperationException($"Cannot replace step '{StepId}' with '{replacement.ReplaceId}'. The ID of the replacement must match the replaced step."); } - if (Afters == null) + BehaviorType = replacement.BehaviorType; + factoryMethod = replacement.FactoryMethod; + + if (!string.IsNullOrWhiteSpace(replacement.Description)) { - Afters = new List(); + Description = replacement.Description; } - - Afters.Add(new Dependency(id, true)); } - internal static RegisterStep Create(WellKnownStep wellKnownStep, Type behavior, string description) + internal IBehavior CreateBehavior(IBuilder defaultBuilder) { - return new DefaultRegisterStep(behavior, wellKnownStep, description); + var behavior = factoryMethod != null + ? factoryMethod(defaultBuilder) + : (IBehavior) defaultBuilder.Build(BehaviorType); + + return behavior; } - internal static RegisterStep Create(string pipelineStep, Type behavior, string description) + + internal static RegisterStep Create(string pipelineStep, Type behavior, string description, Func factoryMethod = null) { - return new DefaultRegisterStep(behavior, pipelineStep, description); + return new DefaultRegisterStep(behavior, pipelineStep, description, factoryMethod); } - + + Func factoryMethod; + class DefaultRegisterStep : RegisterStep { - public DefaultRegisterStep(Type behavior, string stepId, string description) - : base(stepId, behavior, description) + public DefaultRegisterStep(Type behavior, string stepId, string description, Func factoryMethod) + : base(stepId, behavior, description, factoryMethod) { } } diff --git a/src/NServiceBus.Core/Pipeline/RegisterStepExtensions.cs b/src/NServiceBus.Core/Pipeline/RegisterStepExtensions.cs new file mode 100644 index 00000000000..26d1003d9c1 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/RegisterStepExtensions.cs @@ -0,0 +1,56 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using Pipeline; + + static class RegisterStepExtensions + { + public static bool IsStageConnector(this RegisterStep step) + { + return typeof(IStageConnector).IsAssignableFrom(step.BehaviorType); + } + + public static Type GetContextType(this Type behaviorType) + { + var behaviorInterface = behaviorType.GetBehaviorInterface(); + return behaviorInterface.GetGenericArguments()[0]; + } + + public static bool IsBehavior(this Type behaviorType) + { + return behaviorType.GetInterfaces() + .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == BehaviorInterfaceType); + } + + public static Type GetBehaviorInterface(this Type behaviorType) + { + return behaviorType.GetInterfaces() + .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == BehaviorInterfaceType); + } + + public static Type GetOutputContext(this RegisterStep step) + { + return step.BehaviorType.GetOutputContext(); + } + + public static Type GetOutputContext(this Type behaviorType) + { + var behaviorInterface = GetBehaviorInterface(behaviorType); + return behaviorInterface.GetGenericArguments()[1]; + } + + public static Type GetInputContext(this RegisterStep step) + { + return step.BehaviorType.GetInputContext(); + } + + public static Type GetInputContext(this Type behaviorType) + { + var behaviorInterface = GetBehaviorInterface(behaviorType); + return behaviorInterface.GetGenericArguments()[0]; + } + + static Type BehaviorInterfaceType = typeof(IBehavior<,>); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/RemoveStep.cs b/src/NServiceBus.Core/Pipeline/RemoveStep.cs index 094a27c1b74..d9d02bacf11 100644 --- a/src/NServiceBus.Core/Pipeline/RemoveStep.cs +++ b/src/NServiceBus.Core/Pipeline/RemoveStep.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { class RemoveStep { diff --git a/src/NServiceBus.Core/Pipeline/ReplaceBehavior.cs b/src/NServiceBus.Core/Pipeline/ReplaceBehavior.cs deleted file mode 100644 index 9b19c31cb02..00000000000 --- a/src/NServiceBus.Core/Pipeline/ReplaceBehavior.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - - class ReplaceBehavior - { - public ReplaceBehavior(string idToReplace, Type behavior, string description = null) - { - ReplaceId = idToReplace; - Description = description; - BehaviorType = behavior; - } - - public string ReplaceId { get; private set; } - public string Description { get; private set; } - public Type BehaviorType { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/ReplaceStep.cs b/src/NServiceBus.Core/Pipeline/ReplaceStep.cs new file mode 100644 index 00000000000..839bada5f8c --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/ReplaceStep.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System; + using ObjectBuilder; + using Pipeline; + + class ReplaceStep + { + public ReplaceStep(string idToReplace, Type behavior, string description = null, Func factoryMethod = null) + { + ReplaceId = idToReplace; + Description = description; + BehaviorType = behavior; + FactoryMethod = factoryMethod; + } + + public string ReplaceId { get; } + public string Description { get; } + public Type BehaviorType { get; } + public Func FactoryMethod { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/RootContext.cs b/src/NServiceBus.Core/Pipeline/RootContext.cs new file mode 100644 index 00000000000..dea9b6dd222 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/RootContext.cs @@ -0,0 +1,14 @@ +namespace NServiceBus +{ + using ObjectBuilder; + + class RootContext : BehaviorContext + { + public RootContext(IBuilder builder, IPipelineCache pipelineCache, IEventAggregator eventAggregator) : base(null) + { + Set(builder); + Set(pipelineCache); + Set(eventAggregator); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/SatelliteDefinition.cs b/src/NServiceBus.Core/Pipeline/SatelliteDefinition.cs new file mode 100644 index 00000000000..e332ddc9414 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/SatelliteDefinition.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using ObjectBuilder; + using Transport; + + class SatelliteDefinition + { + public SatelliteDefinition(string name, string receiveAddress, TransportTransactionMode requiredTransportTransactionMode, PushRuntimeSettings runtimeSettings, Func recoverabilityPolicy, Func onMessage) + { + Name = name; + ReceiveAddress = receiveAddress; + RequiredTransportTransactionMode = requiredTransportTransactionMode; + RuntimeSettings = runtimeSettings; + RecoverabilityPolicy = recoverabilityPolicy; + OnMessage = onMessage; + } + + public string Name { get; private set; } + + public string ReceiveAddress { get; private set; } + + public TransportTransactionMode RequiredTransportTransactionMode { get; private set; } + + public PushRuntimeSettings RuntimeSettings { get; private set; } + + public Func RecoverabilityPolicy { get; private set; } + + public Func OnMessage { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/SatellitePipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/SatellitePipelineExecutor.cs new file mode 100644 index 00000000000..fd73b93d6b5 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/SatellitePipelineExecutor.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using ObjectBuilder; + using Transport; + + class SatellitePipelineExecutor : IPipelineExecutor + { + public SatellitePipelineExecutor(IBuilder builder, SatelliteDefinition definition) + { + this.builder = builder; + satelliteDefinition = definition; + } + + public Task Invoke(MessageContext messageContext) + { + messageContext.Context.Set(messageContext.TransportTransaction); + + return satelliteDefinition.OnMessage(builder, messageContext); + } + + SatelliteDefinition satelliteDefinition; + IBuilder builder; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/SnapshotRegion.cs b/src/NServiceBus.Core/Pipeline/SnapshotRegion.cs deleted file mode 100644 index a949d49cfdc..00000000000 --- a/src/NServiceBus.Core/Pipeline/SnapshotRegion.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - using Janitor; - - [SkipWeaving] - class SnapshotRegion : IDisposable - { - public SnapshotRegion(dynamic chain) - { - this.chain = chain; - chain.TakeSnapshot(); - } - - public void Dispose() - { - chain.DeleteSnapshot(); - } - - readonly dynamic chain; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/StageConnector.cs b/src/NServiceBus.Core/Pipeline/StageConnector.cs new file mode 100644 index 00000000000..dedd921d439 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/StageConnector.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// Connects two stages of the pipeline. + /// + public abstract class StageConnector : IStageConnector + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + { + /// + /// Contains information about the pipeline this behavior is part of. + /// + /// + public abstract Task Invoke(TFromContext context, Func stage); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/StageForkConnector.cs b/src/NServiceBus.Core/Pipeline/StageForkConnector.cs new file mode 100644 index 00000000000..4159a4ead8e --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/StageForkConnector.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.Pipeline +{ + using System; + using System.Threading.Tasks; + + /// + /// Connects two stages of a pipeline and forks into an independent pipeline. + /// + /// The context to connect from. + /// The context to connect to. + /// The context to fork an independent pipeline to. + public abstract class StageForkConnector : IStageForkConnector + where TFromContext : IBehaviorContext + where TToContext : IBehaviorContext + where TForkContext : IBehaviorContext + { + /// + public Task Invoke(TFromContext context, Func next) + { + Guard.AgainstNull(nameof(context), context); + Guard.AgainstNull(nameof(next), next); + + return Invoke(context, next, ctx => + { + var cache = ctx.Extensions.Get(); + var pipeline = cache.Pipeline(); + return pipeline.Invoke(ctx); + }); + } + + /// + public abstract Task Invoke(TFromContext context, Func stage, Func fork); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/StepEnded.cs b/src/NServiceBus.Core/Pipeline/StepEnded.cs deleted file mode 100644 index 3e1589b003e..00000000000 --- a/src/NServiceBus.Core/Pipeline/StepEnded.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - - /// - /// Step execution ended. - /// - public struct StepEnded - { - /// - /// Creates an instance of . - /// - /// Elapsed time. - public StepEnded(TimeSpan duration) - { - this.duration = duration; - } - - /// - /// Elapsed time. - /// - public TimeSpan Duration - { - get { return duration; } - } - - readonly TimeSpan duration; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/StepRegistrationsCoordinator.cs b/src/NServiceBus.Core/Pipeline/StepRegistrationsCoordinator.cs index 2ff016668ec..192c2cd7c3e 100644 --- a/src/NServiceBus.Core/Pipeline/StepRegistrationsCoordinator.cs +++ b/src/NServiceBus.Core/Pipeline/StepRegistrationsCoordinator.cs @@ -1,21 +1,21 @@ -namespace NServiceBus.Pipeline +namespace NServiceBus { using System; using System.Collections.Generic; using System.Linq; - using Logging; + using Pipeline; class StepRegistrationsCoordinator { - public StepRegistrationsCoordinator(List removals, List replacements) + public StepRegistrationsCoordinator(List removals, List replacements) { this.removals = removals; this.replacements = replacements; } - public void Register(WellKnownStep wellKnownStep, Type behavior, string description) + public void Register(string pipelineStep, Type behavior, string description) { - additions.Add(RegisterStep.Create(wellKnownStep, behavior, description)); + additions.Add(RegisterStep.Create(pipelineStep, behavior, description)); } public void Register(RegisterStep rego) @@ -23,205 +23,18 @@ public void Register(RegisterStep rego) additions.Add(rego); } - public IEnumerable BuildRuntimeModel() - { - var registrations = CreateRegistrationsList(); - - return Sort(registrations); - } - - IEnumerable CreateRegistrationsList() - { - var registrations = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - var listOfBeforeAndAfterIds = new List(); - - // Let's do some validation too - - //Step 1: validate that additions are unique - foreach (var metadata in additions) - { - if (!registrations.ContainsKey(metadata.StepId)) - { - registrations.Add(metadata.StepId, metadata); - if (metadata.Afters != null) - { - listOfBeforeAndAfterIds.AddRange(metadata.Afters.Select(a=>a.Id)); - } - if (metadata.Befores != null) - { - listOfBeforeAndAfterIds.AddRange(metadata.Befores.Select(b=>b.Id)); - } - - continue; - } - - var message = string.Format("Step registration with id '{0}' is already registered for '{1}'", metadata.StepId, registrations[metadata.StepId].BehaviorType); - throw new Exception(message); - } - - // Step 2: do replacements - foreach (var metadata in replacements) - { - if (!registrations.ContainsKey(metadata.ReplaceId)) - { - var message = string.Format("You can only replace an existing step registration, '{0}' registration does not exist!", metadata.ReplaceId); - throw new Exception(message); - } - - registrations[metadata.ReplaceId].BehaviorType = metadata.BehaviorType; - if (!String.IsNullOrEmpty(metadata.Description)) - { - registrations[metadata.ReplaceId].Description = metadata.Description; - } - } - - // Step 3: validate the removals - foreach (var metadata in removals.Distinct(new CaseInsensitiveIdComparer())) - { - if (!registrations.ContainsKey(metadata.RemoveId)) - { - var message = string.Format("You cannot remove step registration with id '{0}', registration does not exist!", metadata.RemoveId); - throw new Exception(message); - } - - if (listOfBeforeAndAfterIds.Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase)) - { - var add = additions.First(mr => (mr.Befores != null && mr.Befores.Select(b=>b.Id).Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase)) || - (mr.Afters != null && mr.Afters.Select(b=>b.Id).Contains(metadata.RemoveId, StringComparer.CurrentCultureIgnoreCase))); - - var message = string.Format("You cannot remove step registration with id '{0}', registration with id {1} depends on it!", metadata.RemoveId, add.StepId); - throw new Exception(message); - } - - registrations.Remove(metadata.RemoveId); - } - - return registrations.Values; - } - - static IEnumerable Sort(IEnumerable registrations) + public List BuildPipelineModelFor() where TRootContext : IBehaviorContext { - // Step 1: create nodes for graph - var nameToNodeDict = new Dictionary(); - var allNodes = new List(); - foreach (var rego in registrations) - { - // create entries to preserve order within - var node = new Node - { - Rego = rego - }; - nameToNodeDict[rego.StepId] = node; - allNodes.Add(node); - } - - // Step 2: create edges from InsertBefore/InsertAfter values - foreach (var node in allNodes) - { - if (node.Rego.Befores != null) - { - foreach (var beforeReference in node.Rego.Befores) - { - Node referencedNode; - if (nameToNodeDict.TryGetValue(beforeReference.Id, out referencedNode)) - { - referencedNode.previous.Add(node); - } - else - { - var message = string.Format("Registration '{0}' specified in the insertbefore of the '{1}' step does not exist!", beforeReference.Id, node.Rego.StepId); - - if (!beforeReference.Enforce) - { - Logger.Info(message); - } - else - { - throw new Exception(message); - } - } - } - } - - if (node.Rego.Afters != null) - { - foreach (var afterReference in node.Rego.Afters) - { - Node referencedNode; - if (nameToNodeDict.TryGetValue(afterReference.Id, out referencedNode)) - { - node.previous.Add(referencedNode); - } - else - { - var message = string.Format("Registration '{0}' specified in the insertafter of the '{1}' step does not exist!", afterReference.Id, node.Rego.StepId); - - if (!afterReference.Enforce) - { - Logger.Info(message); - } - else - { - throw new Exception(message); - } - } - } - } - } + var relevantRemovals = removals.Where(removal => additions.Any(a => a.StepId == removal.RemoveId)).ToList(); + var relevantReplacements = replacements.Where(removal => additions.Any(a => a.StepId == removal.ReplaceId)).ToList(); - // Step 3: Perform Topological Sort - var output = new List(); - foreach (var node in allNodes) - { - node.Visit(output); - } + var piplineModelBuilder = new PipelineModelBuilder(typeof(TRootContext), additions, relevantRemovals, relevantReplacements); - return output; + return piplineModelBuilder.Build(); } List additions = new List(); List removals; - List replacements; - - static ILog Logger = LogManager.GetLogger(); - - class CaseInsensitiveIdComparer : IEqualityComparer - { - public bool Equals(RemoveStep x, RemoveStep y) - { - return x.RemoveId.Equals(y.RemoveId, StringComparison.CurrentCultureIgnoreCase); - } - - public int GetHashCode(RemoveStep obj) - { - return obj.RemoveId.ToLower().GetHashCode(); - } - } - - class Node - { - internal void Visit(ICollection output) - { - if (visited) - { - return; - } - visited = true; - foreach (var n in previous) - { - n.Visit(output); - } - output.Add(Rego); - } - - internal RegisterStep Rego; - internal List previous = new List(); - bool visited; - } - - public void Register(string pipelineStep, Type behavior, string description) - { - Register(WellKnownStep.Create(pipelineStep), behavior, description); - } + List replacements; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/StepStarted.cs b/src/NServiceBus.Core/Pipeline/StepStarted.cs deleted file mode 100644 index 855afd84129..00000000000 --- a/src/NServiceBus.Core/Pipeline/StepStarted.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus.Pipeline -{ - using System; - - /// - /// Step execution started. - /// - public struct StepStarted - { - /// - /// Creates an instance of . - /// - /// Step identifier. - /// Behavior type. - /// Observable for when step ends. - public StepStarted(string stepId, Type behavior, IObservable stepEnded) - { - this.stepId = stepId; - this.behavior = behavior; - this.stepEnded = stepEnded; - } - - /// - /// Behavior type. - /// - public Type Behavior - { - get { return behavior; } - } - - /// - /// Step identifier. - /// - public string StepId - { - get { return stepId; } - } - - /// - /// Step ended. - /// - public IObservable Ended - { - get { return stepEnded; } - } - - readonly Type behavior; - readonly IObservable stepEnded; - readonly string stepId; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/WellKnownStep.cs b/src/NServiceBus.Core/Pipeline/WellKnownStep.cs deleted file mode 100644 index d93224c4dec..00000000000 --- a/src/NServiceBus.Core/Pipeline/WellKnownStep.cs +++ /dev/null @@ -1,103 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus.Pipeline -{ - using System; - using Unicast.Messages; - - /// - /// Well known steps. - /// - public class WellKnownStep - { - readonly string stepId; - - WellKnownStep(string stepId) - { - if (string.IsNullOrWhiteSpace(stepId)) - { - throw new InvalidOperationException("PipelineStep cannot be empty string or null. Use a valid name instead."); - } - this.stepId = stepId; - } - - - internal static WellKnownStep Create(string customStepId) - { - return new WellKnownStep(customStepId); - } - - public static implicit operator string(WellKnownStep step) - { - return step.stepId; - } - - /// - /// Statistics analysis - /// - public static WellKnownStep ProcessingStatistics = new WellKnownStep("ProcessingStatistics"); - /// - /// Auditing - /// - public static readonly WellKnownStep AuditProcessedMessage = new WellKnownStep("AuditProcessedMessage"); - /// - /// Child Container creator. - /// - public static readonly WellKnownStep CreateChildContainer = new WellKnownStep("CreateChildContainer"); - /// - /// Executes UoWs. - /// - public static readonly WellKnownStep ExecuteUnitOfWork = new WellKnownStep("ExecuteUnitOfWork"); - /// - /// Runs incoming mutation for . - /// - public static readonly WellKnownStep MutateIncomingTransportMessage = new WellKnownStep("MutateIncomingTransportMessage"); - /// - /// Send messages to the wire. - /// - public static readonly WellKnownStep DispatchMessageToTransport = new WellKnownStep("DispatchMessageToTransport"); - /// - /// Invokes IHandleMessages{T}.Handle(T) - /// - public static readonly WellKnownStep InvokeHandlers = new WellKnownStep("InvokeHandlers"); - /// - /// Deserializes all logical messages from the transport message. - /// - public static readonly WellKnownStep DeserializeMessages = new WellKnownStep("DeserializeMessages"); - /// - /// Runs incoming mutation for each logical message. - /// - public static readonly WellKnownStep MutateIncomingMessages = new WellKnownStep("MutateIncomingMessages"); - /// - /// Loads all handlers to be executed. - /// - public static readonly WellKnownStep LoadHandlers = new WellKnownStep("LoadHandlers"); - /// - /// Invokes the saga code. - /// - public static readonly WellKnownStep InvokeSaga = new WellKnownStep("InvokeSaga"); - /// - /// Loops through all . - /// - public static readonly WellKnownStep ExecuteLogicalMessages = new WellKnownStep("ExecuteLogicalMessages"); - /// - /// Ensures best practices are met. - /// - public static readonly WellKnownStep EnforceBestPractices = new WellKnownStep("EnforceBestPractices"); - /// - /// Runs outgoing mutation for each logical message. - /// - public static readonly WellKnownStep MutateOutgoingMessages = new WellKnownStep("MutateOutgoingMessages"); - /// - /// Creates the protocol messages to send out. - /// - public static readonly WellKnownStep CreatePhysicalMessage = new WellKnownStep("CreatePhysicalMessage"); - /// - /// Serializes messages. - /// - public static readonly WellKnownStep SerializeMessage = new WellKnownStep("SerializeMessage"); - /// - /// Runs outgoing mutation for . - /// - public static readonly WellKnownStep MutateOutgoingTransportMessage = new WellKnownStep("MutateOutgoingTransportMessage"); - } -} diff --git a/src/NServiceBus.Core/Properties/AssemblyInfo.cs b/src/NServiceBus.Core/Properties/AssemblyInfo.cs index c04bef741dd..483a1aa0ffa 100644 --- a/src/NServiceBus.Core/Properties/AssemblyInfo.cs +++ b/src/NServiceBus.Core/Properties/AssemblyInfo.cs @@ -1,10 +1,14 @@ using System; using System.Reflection; using System.Runtime.InteropServices; +using NServiceBus; [assembly: AssemblyTitle("NServiceBus.Core")] -[assembly: AssemblyCopyright("Copyright 2010-2014 NServiceBus. All rights reserved")] +[assembly: AssemblyCopyright("Copyright NServiceBus Ltd. All rights reserved")] [assembly: AssemblyProduct("NServiceBus.Core")] [assembly: AssemblyCompany("NServiceBus Ltd.")] [assembly: ComVisible(false)] [assembly: CLSCompliant(true)] + +// TODO: Introduce proper way to automatically generate these dates before first hotfix/next minor +[assembly: ReleaseDate("2016-10-05", "2016-10-05")] diff --git a/src/NServiceBus.Core/Properties/Resharper.Annotations.g.cs b/src/NServiceBus.Core/Properties/Resharper.Annotations.g.cs new file mode 100644 index 00000000000..b1d99e85aeb --- /dev/null +++ b/src/NServiceBus.Core/Properties/Resharper.Annotations.g.cs @@ -0,0 +1,1000 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace JetBrains.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + internal sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + internal sealed class NotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + internal sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + internal sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + internal sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + internal sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute(string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + internal sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) + { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + internal sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + internal sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] + public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + internal sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] + public ImplicitUseKindFlags UseKindFlags { get; private set; } + [UsedImplicitly] + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + internal enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + internal enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + internal sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | + AttributeTargets.Method)] + internal sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + public string BasePath { get; private set; } + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + internal sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// > + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + public AspMvcActionAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + public AspMvcAreaAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + public AspMvcControllerAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + internal sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + internal sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + public HtmlElementAttributesAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + internal sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] + public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + internal sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + internal enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + internal enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RegexPatternAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + internal sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + internal sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute(string tagName, Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + public string TagName { get; private set; } + public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute(string type, string fieldName) + { + Type = type; + FieldName = fieldName; + } + + public string Type { get; private set; } + public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RazorWriteMethodParameterAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class NoReorder : Attribute { } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Properties/Resources.Designer.cs b/src/NServiceBus.Core/Properties/Resources.Designer.cs index 7e12448859b..298639fa893 100644 --- a/src/NServiceBus.Core/Properties/Resources.Designer.cs +++ b/src/NServiceBus.Core/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/NServiceBus.Core/PublishOptions.cs b/src/NServiceBus.Core/PublishOptions.cs new file mode 100644 index 00000000000..53738a1eb1a --- /dev/null +++ b/src/NServiceBus.Core/PublishOptions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Allows the users to control how the publish is performed. + /// + public class PublishOptions : ExtendableOptions + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DefaultRecoverabilityPolicy.cs b/src/NServiceBus.Core/Recoverability/DefaultRecoverabilityPolicy.cs new file mode 100644 index 00000000000..68d1ad26b27 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DefaultRecoverabilityPolicy.cs @@ -0,0 +1,101 @@ +namespace NServiceBus +{ + using System; + using Logging; + using Transport; + + + /// + /// The default recoverability policy. + /// + public static class DefaultRecoverabilityPolicy + { + /// + /// Invokes the default recovery policy. + /// + /// The recoverability configuration. + /// The error context. + /// The recoverability action. + public static RecoverabilityAction Invoke(RecoverabilityConfig config, ErrorContext errorContext) + { + if (errorContext.Exception is MessageDeserializationException) + { + return RecoverabilityAction.MoveToError(config.Failed.ErrorQueue); + } + + if (errorContext.ImmediateProcessingFailures <= config.Immediate.MaxNumberOfRetries) + { + return RecoverabilityAction.ImmediateRetry(); + } + + TimeSpan delay; + if (TryGetDelay(errorContext.Message, errorContext.DelayedDeliveriesPerformed, config.Delayed, out delay)) + { + return RecoverabilityAction.DelayedRetry(delay); + } + + return RecoverabilityAction.MoveToError(config.Failed.ErrorQueue); + } + + static bool TryGetDelay(IncomingMessage message, int delayedDeliveriesPerformed, DelayedConfig config, out TimeSpan delay) + { + delay = TimeSpan.MinValue; + + if (config.MaxNumberOfRetries == 0) + { + return false; + } + + if (delayedDeliveriesPerformed >= config.MaxNumberOfRetries) + { + return false; + } + + if (HasReachedMaxTime(message)) + { + return false; + } + + delay = TimeSpan.FromTicks(config.TimeIncrease.Ticks*(delayedDeliveriesPerformed + 1)); + + return true; + } + + static bool HasReachedMaxTime(IncomingMessage message) + { + string timestampHeader; + + if (!message.Headers.TryGetValue(Headers.DelayedRetriesTimestamp, out timestampHeader)) + { + return false; + } + + if (string.IsNullOrEmpty(timestampHeader)) + { + return false; + } + + try + { + var handledAt = DateTimeExtensions.ToUtcDateTime(timestampHeader); + + var now = DateTime.UtcNow; + if (now > handledAt.AddDays(1)) + { + return true; + } + } + // ReSharper disable once EmptyGeneralCatchClause + // this code won't usually throw but in case a user has decided to hack a message/headers and for some bizarre reason + // they changed the date and that parse fails, we want to make sure that doesn't prevent the message from being + // forwarded to the error queue. + catch (Exception) + { + } + + return false; + } + + static ILog Logger = LogManager.GetLogger(typeof(DefaultRecoverabilityPolicy)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DelayedConfig.cs b/src/NServiceBus.Core/Recoverability/DelayedConfig.cs new file mode 100644 index 00000000000..0ba06430658 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DelayedConfig.cs @@ -0,0 +1,35 @@ +namespace NServiceBus +{ + using System; + + /// + /// Provides information about the delayed retries configuration. + /// + public class DelayedConfig + { + /// + /// Creates a new delayed retries configuration. + /// + /// The maximum number of delayed retries. + /// The time of increase for individual delayed retries. + public DelayedConfig(int maxNumberOfRetries, TimeSpan timeIncrease) + { + Guard.AgainstNegative(nameof(maxNumberOfRetries), maxNumberOfRetries); + Guard.AgainstNegative(nameof(timeIncrease), timeIncrease); + + MaxNumberOfRetries = maxNumberOfRetries; + TimeIncrease = timeIncrease; + } + + /// + /// Gets the configured maximum number of immediate retries. + /// + /// Zero means no retries possible. + public int MaxNumberOfRetries { get; } + + /// + /// Gets the configured time of increase for individual delayed retries. + /// + public TimeSpan TimeIncrease { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DelayedRetries/DelayedRetryMessage.cs b/src/NServiceBus.Core/Recoverability/DelayedRetries/DelayedRetryMessage.cs new file mode 100644 index 00000000000..3659ffb2ff0 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DelayedRetries/DelayedRetryMessage.cs @@ -0,0 +1,46 @@ +namespace NServiceBus.Faults +{ + using System; + using System.Collections.Generic; + + /// + /// Delayed Retry event data. + /// + public class DelayedRetryMessage + { + /// + /// Creates a new instance of . + /// + /// Message headers. + /// Message body. + /// Exception thrown. + /// Number of retry attempt. + public DelayedRetryMessage(Dictionary headers, byte[] body, Exception exception, int retryAttempt) + { + Headers = headers; + Body = body; + Exception = exception; + RetryAttempt = retryAttempt; + } + + /// + /// Gets the message headers. + /// + public Dictionary Headers { get; } + + /// + /// Gets a byte array to the body content of the message. + /// + public byte[] Body { get; } + + /// + /// The exception that caused this message to fail. + /// + public Exception Exception { get; } + + /// + /// Number of retry attempt. + /// + public int RetryAttempt { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DelayedRetriesHeaderExtensions.cs b/src/NServiceBus.Core/Recoverability/DelayedRetriesHeaderExtensions.cs new file mode 100644 index 00000000000..b696c596c92 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DelayedRetriesHeaderExtensions.cs @@ -0,0 +1,33 @@ +namespace NServiceBus +{ + using System; + using Transport; + + static class DelayedRetriesHeaderExtensions + { + public static int GetDelayedDeliveriesPerformed(this IncomingMessage message) + { + string value; + if (message.Headers.TryGetValue(Headers.DelayedRetries, out value)) + { + int i; + if (int.TryParse(value, out i)) + { + return i; + } + } + + return 0; + } + + public static void SetCurrentDelayedDeliveries(this OutgoingMessage message, int currentDelayedRetry) + { + message.Headers[Headers.DelayedRetries] = currentDelayedRetry.ToString(); + } + + public static void SetDelayedDeliveryTimestamp(this OutgoingMessage message, DateTime timestamp) + { + message.Headers[Headers.DelayedRetriesTimestamp] = DateTimeExtensions.ToWireFormattedString(timestamp); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DelayedRetry.cs b/src/NServiceBus.Core/Recoverability/DelayedRetry.cs new file mode 100644 index 00000000000..4e4afad96c0 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DelayedRetry.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using System; + + /// + /// Inidicates recoverability is required to delay retry the current message. + /// + public sealed class DelayedRetry : RecoverabilityAction + { + internal DelayedRetry(TimeSpan delay) + { + Delay = delay; + } + + /// + /// The retry delay. + /// + public TimeSpan Delay { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/DelayedRetryExecutor.cs b/src/NServiceBus.Core/Recoverability/DelayedRetryExecutor.cs new file mode 100644 index 00000000000..6f9d4bc36e0 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/DelayedRetryExecutor.cs @@ -0,0 +1,60 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using DelayedDelivery; + using DeliveryConstraints; + using Extensibility; + using Routing; + using Transport; + + class DelayedRetryExecutor + { + public DelayedRetryExecutor(string endpointInputQueue, IDispatchMessages dispatcher, string timeoutManagerAddress = null) + { + this.timeoutManagerAddress = timeoutManagerAddress; + this.dispatcher = dispatcher; + this.endpointInputQueue = endpointInputQueue; + } + + public async Task Retry(IncomingMessage message, TimeSpan delay, TransportTransaction transportTransaction) + { + var outgoingMessage = new OutgoingMessage(message.MessageId, new Dictionary(message.Headers), message.Body); + + var currentDelayedRetriesAttempt = message.GetDelayedDeliveriesPerformed() + 1; + + outgoingMessage.SetCurrentDelayedDeliveries(currentDelayedRetriesAttempt); + outgoingMessage.SetDelayedDeliveryTimestamp(DateTime.UtcNow); + + UnicastAddressTag messageDestination; + List deliveryConstraints = null; + if (timeoutManagerAddress == null) + { + // transport supports native deferred messages, directly send to input queue with delay constraint: + deliveryConstraints = new List(1) + { + new DelayDeliveryWith(delay) + }; + messageDestination = new UnicastAddressTag(endpointInputQueue); + } + else + { + // transport doesn't support native deferred messages, reroute to timeout manager: + outgoingMessage.Headers[TimeoutManagerHeaders.RouteExpiredTimeoutTo] = endpointInputQueue; + outgoingMessage.Headers[TimeoutManagerHeaders.Expire] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow + delay); + messageDestination = new UnicastAddressTag(timeoutManagerAddress); + } + + var transportOperations = new TransportOperations(new TransportOperation(outgoingMessage, messageDestination, deliveryConstraints: deliveryConstraints)); + + await dispatcher.Dispatch(transportOperations, transportTransaction, new ContextBag()).ConfigureAwait(false); + + return currentDelayedRetriesAttempt; + } + + IDispatchMessages dispatcher; + string endpointInputQueue; + string timeoutManagerAddress; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/FailedConfig.cs b/src/NServiceBus.Core/Recoverability/FailedConfig.cs new file mode 100644 index 00000000000..5e85c5b52e1 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/FailedConfig.cs @@ -0,0 +1,24 @@ +namespace NServiceBus +{ + /// + /// Provides information about the fault configuration. + /// + public class FailedConfig + { + /// + /// Creates a new fault configuration. + /// + /// The address of the error queue. + public FailedConfig(string errorQueue) + { + Guard.AgainstNullAndEmpty(nameof(errorQueue), errorQueue); + + ErrorQueue = errorQueue; + } + + /// + /// Gets the configured standard error queue. + /// + public string ErrorQueue { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/ConfigureError.cs b/src/NServiceBus.Core/Recoverability/Faults/ConfigureError.cs new file mode 100644 index 00000000000..683102edf78 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/ConfigureError.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + /// + /// Contains extension methods to . + /// + public static class ConfigureError + { + /// + /// Configure error queue settings. + /// + /// The instance to apply the settings to. + /// The name of the error queue to use. + public static void SendFailedMessagesTo(this EndpointConfiguration config, string errorQueue) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(errorQueue), errorQueue); + config.Settings.Set("errorQueue", errorQueue); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/ErrorQueueSettings.cs b/src/NServiceBus.Core/Recoverability/Faults/ErrorQueueSettings.cs new file mode 100644 index 00000000000..fe7692f748f --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/ErrorQueueSettings.cs @@ -0,0 +1,75 @@ +namespace NServiceBus +{ + using System; + using Config; + using Logging; + using Settings; + + /// + /// Utility class used to find the configured error queue for an endpoint. + /// + public static class ErrorQueueSettings + { + /// + /// Finds the configured error queue for an endpoint. + /// The error queue can be configured in code using 'EndpointConfiguration.SendFailedMessagesTo()', + /// via the 'Error' attribute of the 'MessageForwardingInCaseOfFaultConfig' configuration section, + /// or using the 'HKEY_LOCAL_MACHINE\SOFTWARE\ParticularSoftware\ServiceBus\ErrorQueue' registry key. + /// + /// The configuration settings of this endpoint. + /// The configured error queue of the endpoint. + /// When the configuration for the endpoint is invalid. + public static string ErrorQueueAddress(this ReadOnlySettings settings) + { + string errorQueue; + + if (settings.TryGet("errorQueue", out errorQueue)) + { + Logger.Debug("Error queue retrieved from code configuration via 'EndpointConfiguration.SendFailedMessagesTo()."); + return errorQueue; + } + + var section = settings.GetConfigSection(); + if (section != null) + { + if (!string.IsNullOrWhiteSpace(section.ErrorQueue)) + { + Logger.Debug("Error queue retrieved from element in config file."); + return section.ErrorQueue; + } + + throw new Exception( + @"'MessageForwardingInCaseOfFaultConfig' configuration section is found but 'ErrorQueue' value is empty. +Take on of the following actions: +- set the error queue at configuration time using 'EndpointConfiguration.SendFailedMessagesTo()' +- Add a valid value to to the app.config. For example: + "); + } + + var registryErrorQueue = RegistryReader.Read("ErrorQueue"); + if (registryErrorQueue != null) + { + if (!string.IsNullOrWhiteSpace(registryErrorQueue)) + { + Logger.Debug("Error queue retrieved from registry settings."); + return registryErrorQueue; + } + throw new Exception( + @"'ErrorQueue' read from registry but the value is empty. +Take on of the following actions: +- set the error queue at configuration time using 'EndpointConfiguration.SendFailedMessagesTo()' +- add a 'MessageForwardingInCaseOfFaultConfig' section to the app.config +- give 'HKEY_LOCAL_MACHINE\SOFTWARE\ParticularSoftware\ServiceBus\ErrorQueue' a valid value for the error queue"); + } + + throw new Exception( + @"Faults forwarding requires an error queue to be specified. +Take on of the following actions: +- set the error queue at configuration time using 'EndpointConfiguration.SendFailedMessagesTo()' +- add a 'MessageForwardingInCaseOfFaultConfig' section to the app.config +- configure a global error queue in the registry using the powershell command: Set-NServiceBusLocalMachineSettings -ErrorQueue {address of error queue}"); + } + + static ILog Logger = LogManager.GetLogger(typeof(ErrorQueueSettings)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/ErrorsNotifications.cs b/src/NServiceBus.Core/Recoverability/Faults/ErrorsNotifications.cs new file mode 100644 index 00000000000..f7d95785713 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/ErrorsNotifications.cs @@ -0,0 +1,72 @@ +namespace NServiceBus.Faults +{ + using System; + using System.Collections.Generic; + using Transport; + + /// + /// Errors notifications. + /// + public partial class ErrorsNotifications + { + /// + /// Notification when a message is moved to the error queue. + /// + public event EventHandler MessageSentToErrorQueue; + + /// + /// Notification when a message fails a immediate retry. + /// + public event EventHandler MessageHasFailedAnImmediateRetryAttempt; + + /// + /// Notification when a message is sent to Delayed Retries queue. + /// + public event EventHandler MessageHasBeenSentToDelayedRetries; + + internal void InvokeMessageHasBeenSentToErrorQueue(IncomingMessage message, Exception exception, string errorQueue) + { + var failedMessage = new FailedMessage( + message.MessageId, + new Dictionary(message.Headers), + CopyOfBody(message.Body), exception, errorQueue); + MessageSentToErrorQueue?.Invoke(this, failedMessage); + } + + internal void InvokeMessageHasFailedAnImmediateRetryAttempt(int immediateRetryAttempt, IncomingMessage message, Exception exception) + { + var retry = new ImmediateRetryMessage( + message.MessageId, + new Dictionary(message.Headers), + CopyOfBody(message.Body), + exception, + immediateRetryAttempt); + MessageHasFailedAnImmediateRetryAttempt?.Invoke(this, retry); + } + + internal void InvokeMessageHasBeenSentToDelayedRetries(int delayedRetryAttempt, IncomingMessage message, Exception exception) + { + var retry = new DelayedRetryMessage( + new Dictionary(message.Headers), + CopyOfBody(message.Body), + exception, + delayedRetryAttempt); + + MessageHasBeenSentToDelayedRetries?.Invoke(this, retry); + } + + static byte[] CopyOfBody(byte[] body) + { + if (body == null) + { + return null; + } + + var copyBody = new byte[body.Length]; + + Buffer.BlockCopy(body, 0, copyBody, 0, body.Length); + + return copyBody; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/FailedMessage.cs b/src/NServiceBus.Core/Recoverability/Faults/FailedMessage.cs new file mode 100644 index 00000000000..09cc6fcaf1b --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/FailedMessage.cs @@ -0,0 +1,69 @@ +namespace NServiceBus.Faults +{ + using System; + using System.Collections.Generic; + + /// + /// Error message event data. + /// + public class FailedMessage + { + /// + /// Creates a new instance of . + /// + /// The id of the failed message. + /// Message headers. + /// Message body. + /// Exception thrown. + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = "FailedMessage(string messageId, Dictionary headers, byte[] body, Exception exception, string errorQueue)")] + public FailedMessage(string messageId, Dictionary headers, byte[] body, Exception exception) + { + throw new NotImplementedException(); + } + + /// + /// Creates a new instance of . + /// + /// The id of the failed message. + /// Message headers. + /// Message body. + /// Exception thrown. + /// Error queue address. + public FailedMessage(string messageId, Dictionary headers, byte[] body, Exception exception, string errorQueue) + { + MessageId = messageId; + Headers = headers; + Body = body; + Exception = exception; + ErrorQueue = errorQueue; + } + + /// + /// Gets the message headers. + /// + public Dictionary Headers { get; } + + /// + /// Gets a byte array to the body content of the message. + /// + public byte[] Body { get; } + + /// + /// The exception that caused this message to fail. + /// + public Exception Exception { get; } + + /// + /// Error queue address to which failed message has been moved. + /// + public string ErrorQueue { get; } + + /// + /// The id of the failed message. + /// + public string MessageId { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Faults/FaultsHeaderKeys.cs b/src/NServiceBus.Core/Recoverability/Faults/FaultsHeaderKeys.cs similarity index 100% rename from src/NServiceBus.Core/Faults/FaultsHeaderKeys.cs rename to src/NServiceBus.Core/Recoverability/Faults/FaultsHeaderKeys.cs diff --git a/src/NServiceBus.Core/Recoverability/Faults/MessageFaulted.cs b/src/NServiceBus.Core/Recoverability/Faults/MessageFaulted.cs new file mode 100644 index 00000000000..23656872a0f --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/MessageFaulted.cs @@ -0,0 +1,15 @@ +namespace NServiceBus +{ + using System; + using Transport; + + class MessageFaulted : MessageProcessingFailed + { + public string ErrorQueue { get; } + + public MessageFaulted(IncomingMessage message, Exception exception, string errorQueue) : base(message, exception) + { + ErrorQueue = errorQueue; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/MessageForwardingInCaseOfFaultConfig.cs b/src/NServiceBus.Core/Recoverability/Faults/MessageForwardingInCaseOfFaultConfig.cs new file mode 100644 index 00000000000..ba335946b26 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/MessageForwardingInCaseOfFaultConfig.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Config +{ + using System.Configuration; + + /// + /// Message Forwarding In Case Of Fault Config. + /// + public class MessageForwardingInCaseOfFaultConfig : ConfigurationSection + { + /// + /// The queue to which errors will be forwarded. + /// + [ConfigurationProperty("ErrorQueue", IsRequired = true)] + public string ErrorQueue + { + get { return this["ErrorQueue"] as string; } + set { this["ErrorQueue"] = value; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/MessageProcessingFailed.cs b/src/NServiceBus.Core/Recoverability/Faults/MessageProcessingFailed.cs new file mode 100644 index 00000000000..87923ec57d4 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/MessageProcessingFailed.cs @@ -0,0 +1,17 @@ +namespace NServiceBus +{ + using System; + using Transport; + + abstract class MessageProcessingFailed + { + public IncomingMessage Message { get; } + public Exception Exception { get; } + + protected MessageProcessingFailed(IncomingMessage message, Exception exception) + { + Message = message; + Exception = exception; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Faults/MessageToBeRetried.cs b/src/NServiceBus.Core/Recoverability/Faults/MessageToBeRetried.cs new file mode 100644 index 00000000000..ec77a6f3d7b --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Faults/MessageToBeRetried.cs @@ -0,0 +1,18 @@ +namespace NServiceBus +{ + using System; + using Transport; + + class MessageToBeRetried : MessageProcessingFailed + { + public int Attempt { get; } + public TimeSpan Delay { get; } + public bool IsImmediateRetry => Delay == TimeSpan.Zero; + + public MessageToBeRetried(int attempt, TimeSpan delay, IncomingMessage message, Exception exception) : base(message, exception) + { + Attempt = attempt; + Delay = delay; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/ImmediateConfig.cs b/src/NServiceBus.Core/Recoverability/ImmediateConfig.cs new file mode 100644 index 00000000000..751bc7779f2 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/ImmediateConfig.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + /// + /// Provides information about the immediate retries configuration. + /// + public class ImmediateConfig + { + /// + /// Creates a new immediate retries configuration. + /// + /// The maximum number of immediate retries. + public ImmediateConfig(int maxNumberOfRetries) + { + Guard.AgainstNegative(nameof(maxNumberOfRetries), maxNumberOfRetries); + + MaxNumberOfRetries = maxNumberOfRetries; + } + + /// + /// Gets the configured maximum number of immediate retries. + /// + /// Zero means no retries possible. + public int MaxNumberOfRetries { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/ImmediateRetries/ImmediateRetryMessage.cs b/src/NServiceBus.Core/Recoverability/ImmediateRetries/ImmediateRetryMessage.cs new file mode 100644 index 00000000000..51da0aacc15 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/ImmediateRetries/ImmediateRetryMessage.cs @@ -0,0 +1,58 @@ +namespace NServiceBus.Faults +{ + using System; + using System.Collections.Generic; + + /// + /// Immediate Retry event data. + /// + public class ImmediateRetryMessage + { + /// + /// Creates a new instance of . + /// + /// The id of the failed message. + /// Message headers. + /// Message body. + /// Exception thrown. + /// Number of retry attempt. + public ImmediateRetryMessage(string messageId, Dictionary headers, byte[] body, Exception exception, int retryAttempt) + { + Guard.AgainstNullAndEmpty(nameof(messageId), messageId); + Guard.AgainstNull(nameof(headers), headers); + Guard.AgainstNull(nameof(body), body); + Guard.AgainstNull(nameof(exception), exception); + + MessageId = messageId; + Headers = headers; + Body = body; + Exception = exception; + RetryAttempt = retryAttempt; + } + + /// + /// Id of the failed message. + /// + public string MessageId { get; } + + /// + /// Gets the message headers. + /// + public Dictionary Headers { get; } + + /// + /// Gets a byte array to the body content of the message. + /// + public byte[] Body { get; } + + /// + /// The exception that caused this message to fail. + /// + public Exception Exception { get; } + + /// + /// Number of retry attempt. + /// + public int RetryAttempt { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/ImmediateRetry.cs b/src/NServiceBus.Core/Recoverability/ImmediateRetry.cs new file mode 100644 index 00000000000..bde8597ba3a --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/ImmediateRetry.cs @@ -0,0 +1,10 @@ +namespace NServiceBus +{ + /// + /// Inidicates recoverability is required to immediately retry the current message. + /// + public sealed class ImmediateRetry : RecoverabilityAction + { + internal ImmediateRetry() { } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/MoveToError.cs b/src/NServiceBus.Core/Recoverability/MoveToError.cs new file mode 100644 index 00000000000..077432f2236 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/MoveToError.cs @@ -0,0 +1,18 @@ +namespace NServiceBus +{ + /// + /// Indicates that recoverability is required to move the current message to the error queue. + /// + public sealed class MoveToError : RecoverabilityAction + { + internal MoveToError(string errorQueue) + { + ErrorQueue = errorQueue; + } + + /// + /// Defines the error queue where the message should be move to. + /// + public string ErrorQueue { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/MoveToErrorsExecutor.cs b/src/NServiceBus.Core/Recoverability/MoveToErrorsExecutor.cs new file mode 100644 index 00000000000..2bc0363f073 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/MoveToErrorsExecutor.cs @@ -0,0 +1,47 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Routing; + using Transport; + + class MoveToErrorsExecutor + { + public MoveToErrorsExecutor(IDispatchMessages dispatcher, Dictionary staticFaultMetadata, Action> headerCustomizations) + { + this.dispatcher = dispatcher; + this.staticFaultMetadata = staticFaultMetadata; + this.headerCustomizations = headerCustomizations; + } + + public Task MoveToErrorQueue(string errorQueueAddress, IncomingMessage message, Exception exception, TransportTransaction transportTransaction) + { + message.RevertToOriginalBodyIfNeeded(); + + var outgoingMessage = new OutgoingMessage(message.MessageId, new Dictionary(message.Headers), message.Body); + + var headers = outgoingMessage.Headers; + headers.Remove(Headers.DelayedRetries); + headers.Remove(Headers.ImmediateRetries); + + ExceptionHeaderHelper.SetExceptionHeaders(headers, exception); + + foreach (var faultMetadata in staticFaultMetadata) + { + headers[faultMetadata.Key] = faultMetadata.Value; + } + + headerCustomizations(headers); + + var transportOperations = new TransportOperations(new TransportOperation(outgoingMessage, new UnicastAddressTag(errorQueueAddress))); + + return dispatcher.Dispatch(transportOperations, transportTransaction, new ContextBag()); + } + + IDispatchMessages dispatcher; + Dictionary staticFaultMetadata; + Action> headerCustomizations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Recoverability.cs b/src/NServiceBus.Core/Recoverability/Recoverability.cs new file mode 100644 index 00000000000..d2c1a1c700a --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Recoverability.cs @@ -0,0 +1,223 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Config; + using ConsistencyGuarantees; + using DelayedDelivery; + using DeliveryConstraints; + using Faults; + using Features; + using Hosting; + using Logging; + using Routing; + using Settings; + using Support; + using Transport; + + class Recoverability : Feature + { + public Recoverability() + { + EnableByDefault(); + DependsOnOptionally(); + + Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"), + "Message recoverability is only relevant for endpoints receiving messages."); + Defaults(settings => + { + settings.SetDefault(NumberOfDelayedRetries, DefaultNumberOfRetries); + settings.SetDefault(DelayedRetriesTimeIncrease, DefaultTimeIncrease); + settings.SetDefault(NumberOfImmediateRetries, 5); + settings.SetDefault(FaultHeaderCustomization, new Action>(headers => { })); + }); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var errorQueue = context.Settings.ErrorQueueAddress(); + context.Settings.Get().BindSending(errorQueue); + + context.Container.ConfigureComponent(b => + { + Func moveToErrorsExecutorFactory = localAddress => + { + var hostInfo = b.Build(); + var staticFaultMetadata = new Dictionary + { + {FaultsHeaderKeys.FailedQ, localAddress}, + {Headers.ProcessingMachine, RuntimeEnvironment.MachineName}, + {Headers.ProcessingEndpoint, context.Settings.EndpointName()}, + {Headers.HostId, hostInfo.HostId.ToString("N")}, + {Headers.HostDisplayName, hostInfo.DisplayName} + }; + + var headerCustomizations = context.Settings.Get>>(FaultHeaderCustomization); + + return new MoveToErrorsExecutor(b.Build(), staticFaultMetadata, headerCustomizations); + }; + + var transactionsOn = context.Settings.GetRequiredTransactionModeForReceives() != TransportTransactionMode.None; + var delayedRetryConfig = GetDelayedRetryConfig(context.Settings, transactionsOn); + var delayedRetriesAvailable = transactionsOn + && (context.Settings.DoesTransportSupportConstraint() || context.Settings.Get().TransportAddress != null); + + Func delayedRetryExecutorFactory = localAddress => + { + if (delayedRetriesAvailable) + { + return new DelayedRetryExecutor( + localAddress, + b.Build(), + context.Settings.DoesTransportSupportConstraint() + ? null + : context.Settings.Get().TransportAddress); + } + + return null; + }; + + var immediateRetryConfig = GetImmediateRetryConfig(context.Settings, transactionsOn); + var immediateRetriesAvailable = transactionsOn; + + Func policy; + if (!context.Settings.TryGet(PolicyOverride, out policy)) + { + policy = DefaultRecoverabilityPolicy.Invoke; + } + + return new RecoverabilityExecutorFactory( + policy, + new RecoverabilityConfig(immediateRetryConfig, delayedRetryConfig, new FailedConfig(errorQueue)), + delayedRetryExecutorFactory, + moveToErrorsExecutorFactory, + immediateRetriesAvailable, + delayedRetriesAvailable); + + }, DependencyLifecycle.SingleInstance); + + RaiseLegacyNotifications(context); + + //HINT: we turn off the legacy retries satellite only when explicitly configured by the user + bool disableLegacyRetriesSatellite; + if (context.Settings.TryGet(DisableLegacyRetriesSatellite, out disableLegacyRetriesSatellite) == false) + { + SetupLegacyRetriesSatellite(context); + } + } + + static ImmediateConfig GetImmediateRetryConfig(ReadOnlySettings settings, bool transactionsOn) + { + if (!transactionsOn) + { + Logger.Warn("Immediate Retries will be disabled. Immediate Retries are not supported when running with TransportTransactionMode.None. Failed messages will be moved to the error queue instead."); + //Transactions must be enabled since Immediate Retries requires the transport to be able to rollback + return new ImmediateConfig(0); + } + + var retriesConfig = settings.GetConfigSection(); + var maxImmediateRetries = retriesConfig?.MaxRetries ?? settings.Get(NumberOfImmediateRetries); + + return new ImmediateConfig(maxImmediateRetries); + } + + static DelayedConfig GetDelayedRetryConfig(ReadOnlySettings settings, bool transactionsOn) + { + if (!transactionsOn) + { + Logger.Warn("Delayed Retries will be disabled. Delayed retries are not supported when running with TransportTransactionMode.None. Failed messages will be moved to the error queue instead."); + //Transactions must be enabled since Delayed Retries requires the transport to be able to rollback + return new DelayedConfig(0, TimeSpan.Zero); + } + + var numberOfRetries = settings.Get(NumberOfDelayedRetries); + var timeIncrease = settings.Get(DelayedRetriesTimeIncrease); + + var retriesConfig = settings.GetConfigSection(); + if (retriesConfig != null) + { + numberOfRetries = retriesConfig.Enabled ? retriesConfig.NumberOfRetries : 0; + timeIncrease = retriesConfig.TimeIncrease; + } + + return new DelayedConfig(numberOfRetries, timeIncrease); + } + + //note: will soon be removed since we're deprecating Notifications in favor of the new notifications + static void RaiseLegacyNotifications(FeatureConfigurationContext context) + { + var legacyNotifications = context.Settings.Get(); + var notifications = context.Settings.Get(); + + notifications.Subscribe(e => + { + if (e.IsImmediateRetry) + { + legacyNotifications.Errors.InvokeMessageHasFailedAnImmediateRetryAttempt(e.Attempt, e.Message, e.Exception); + } + else + { + legacyNotifications.Errors.InvokeMessageHasBeenSentToDelayedRetries(e.Attempt, e.Message, e.Exception); + } + + return TaskEx.CompletedTask; + }); + + notifications.Subscribe(e => + { + legacyNotifications.Errors.InvokeMessageHasBeenSentToErrorQueue(e.Message, e.Exception, e.ErrorQueue); + return TaskEx.CompletedTask; + }); + } + + void SetupLegacyRetriesSatellite(FeatureConfigurationContext context) + { + var retriesQueueLogicalAddress = context.Settings.LogicalAddress().CreateQualifiedAddress("Retries"); + var retriesQueueTransportAddress = context.Settings.GetTransportAddress(retriesQueueLogicalAddress); + + var mainQueueLogicalAddress = context.Settings.LogicalAddress(); + var mainQueueTransportAddress = context.Settings.GetTransportAddress(mainQueueLogicalAddress); + + var requiredTransactionMode = context.Settings.GetRequiredTransactionModeForReceives(); + + context.AddSatelliteReceiver("Legacy Retries Processor", retriesQueueTransportAddress, requiredTransactionMode, new PushRuntimeSettings(maxConcurrency: 1), + (config, errorContext) => + { + return RecoverabilityAction.MoveToError(config.Failed.ErrorQueue); + }, + (builder, messageContext) => + { + var messageDispatcher = builder.Build(); + + var outgoingHeaders = messageContext.Headers; + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.Reason"); + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.ExceptionType"); + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.InnerExceptionType"); + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.HelpLink"); + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.Message"); + outgoingHeaders.Remove("NServiceBus.ExceptionInfo.Source"); + outgoingHeaders.Remove("NServiceBus.FailedQ"); + outgoingHeaders.Remove("NServiceBus.TimeOfFailure"); + + //HINT: this header is added by v3 when doing SLR + outgoingHeaders.Remove("NServiceBus.OriginalId"); + + var outgoingMessage = new OutgoingMessage(messageContext.MessageId, outgoingHeaders, messageContext.Body); + var outgoingOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(mainQueueTransportAddress)); + + return messageDispatcher.Dispatch(new TransportOperations(outgoingOperation), messageContext.TransportTransaction, messageContext.Context); + }); + } + + public const string NumberOfDelayedRetries = "Recoverability.Delayed.DefaultPolicy.Retries"; + public const string DelayedRetriesTimeIncrease = "Recoverability.Delayed.DefaultPolicy.Timespan"; + public const string NumberOfImmediateRetries = "Recoverability.Immediate.Retries"; + public const string FaultHeaderCustomization = "Recoverability.Failed.FaultHeaderCustomization"; + public const string PolicyOverride = "Recoverability.CustomPolicy"; + public const string DisableLegacyRetriesSatellite = "Recoverability.DisableLegacyRetriesSatellite"; + + static ILog Logger = LogManager.GetLogger(); + internal static int DefaultNumberOfRetries = 3; + internal static TimeSpan DefaultTimeIncrease = TimeSpan.FromSeconds(10); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityAction.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityAction.cs new file mode 100644 index 00000000000..c6edc60134c --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityAction.cs @@ -0,0 +1,50 @@ +namespace NServiceBus +{ + using System; + + /// + /// Abstraction representing any recoverability action. + /// + public abstract class RecoverabilityAction + { + /// + /// Initializes a new instance of a recoverability action. + /// + protected internal RecoverabilityAction() + { + } + + /// + /// Creates an immediate retry recoverability action. + /// + /// Immediate retry action. + public static ImmediateRetry ImmediateRetry() + { + return CachedImmediateRetry; + } + + /// + /// Creates a new delayed retry recoverability action. + /// + /// Delivery delay. + /// Delayed retry action. + public static DelayedRetry DelayedRetry(TimeSpan timeSpan) + { + Guard.AgainstNegative(nameof(timeSpan), timeSpan); + + return new DelayedRetry(timeSpan); + } + + /// + /// Creates a move to error recoverability action. + /// + /// The address of the error queue. + /// Move to error action. + public static MoveToError MoveToError(string errorQueue) + { + return new MoveToError(errorQueue); + } + + static ImmediateRetry CachedImmediateRetry = new ImmediateRetry(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityConfig.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityConfig.cs new file mode 100644 index 00000000000..ca7997bae31 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityConfig.cs @@ -0,0 +1,40 @@ +namespace NServiceBus +{ + /// + /// Provides information about the recoverability configuration. + /// + public class RecoverabilityConfig + { + /// + /// Creates a new recoverability configuration. + /// + /// The immediate retries configuration. + /// The delayed retries configuration. + /// The failed retries configuration. + public RecoverabilityConfig(ImmediateConfig immediateConfig, DelayedConfig delayedConfig, FailedConfig failedConfig) + { + Guard.AgainstNull(nameof(immediateConfig), immediateConfig); + Guard.AgainstNull(nameof(delayedConfig), delayedConfig); + Guard.AgainstNull(nameof(failedConfig), failedConfig); + + Immediate = immediateConfig; + Delayed = delayedConfig; + Failed = failedConfig; + } + + /// + /// Exposes the immediate retries configuration. + /// + public ImmediateConfig Immediate { get; } + + /// + /// Exposes the delayed retries configuration. + /// + public DelayedConfig Delayed { get; } + + /// + /// Exposes the failed retries configuration. + /// + public FailedConfig Failed { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityExecutor.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityExecutor.cs new file mode 100644 index 00000000000..00453f8ab67 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityExecutor.cs @@ -0,0 +1,117 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Logging; + using Transport; + + class RecoverabilityExecutor + { + public RecoverabilityExecutor(bool raiseRecoverabilityNotifications, bool immediateRetriesAvailable, bool delayedRetriesAvailable, Func recoverabilityPolicy, RecoverabilityConfig configuration, IEventAggregator eventAggregator, DelayedRetryExecutor delayedRetryExecutor, MoveToErrorsExecutor moveToErrorsExecutor) + { + this.configuration = configuration; + this.recoverabilityPolicy = recoverabilityPolicy; + this.eventAggregator = eventAggregator; + this.delayedRetryExecutor = delayedRetryExecutor; + this.moveToErrorsExecutor = moveToErrorsExecutor; + this.immediateRetriesAvailable = immediateRetriesAvailable; + this.delayedRetriesAvailable = delayedRetriesAvailable; + + raiseNotifications = raiseRecoverabilityNotifications; + } + + public Task Invoke(ErrorContext errorContext) + { + var recoveryAction = recoverabilityPolicy(configuration, errorContext); + + // When we can't do immediate retries and policy did not honor MaxNumberOfRetries for ImmediateRetries + if (recoveryAction is ImmediateRetry && !immediateRetriesAvailable) + { + Logger.Warn("Recoverability policy requested ImmediateRetry however immediate retries are not available with the current endpoint configuration. Moving message to error queue instead."); + return MoveToError(errorContext, configuration.Failed.ErrorQueue); + } + + if (recoveryAction is ImmediateRetry) + { + return RaiseImmediateRetryNotifications(errorContext); + } + + // When we can't do delayed retries, a policy customization probably didn't honor MaxNumberOfRetries for DelayedRetries + if (recoveryAction is DelayedRetry && !delayedRetriesAvailable) + { + Logger.Warn("Recoverability policy requested DelayedRetry however delayed delivery capability is not available with the current endpoint configuration. Moving message to error queue instead."); + return MoveToError(errorContext, configuration.Failed.ErrorQueue); + } + + var delayedRetryAction = recoveryAction as DelayedRetry; + if (delayedRetryAction != null) + { + return DeferMessage(delayedRetryAction, errorContext); + } + + var moveToError = recoveryAction as MoveToError; + if (moveToError != null) + { + return MoveToError(errorContext, moveToError.ErrorQueue); + } + + Logger.Warn("Recoverability policy returned an unsupported recoverability action. Moving message to error queue instead."); + return MoveToError(errorContext, configuration.Failed.ErrorQueue); + } + + async Task RaiseImmediateRetryNotifications(ErrorContext errorContext) + { + var message = errorContext.Message; + + Logger.Info($"Immediate Retry is going to retry message '{message.MessageId}' because of an exception:", errorContext.Exception); + + if (raiseNotifications) + { + await eventAggregator.Raise(new MessageToBeRetried(errorContext.ImmediateProcessingFailures - 1, TimeSpan.Zero, message, errorContext.Exception)).ConfigureAwait(false); + } + + return ErrorHandleResult.RetryRequired; + } + + async Task MoveToError(ErrorContext errorContext, string errorQueue) + { + var message = errorContext.Message; + + Logger.Error($"Moving message '{message.MessageId}' to the error queue '{ errorQueue }' because processing failed due to an exception:", errorContext.Exception); + + await moveToErrorsExecutor.MoveToErrorQueue(errorQueue, message, errorContext.Exception, errorContext.TransportTransaction).ConfigureAwait(false); + + if (raiseNotifications) + { + await eventAggregator.Raise(new MessageFaulted(message, errorContext.Exception, errorQueue)).ConfigureAwait(false); + } + return ErrorHandleResult.Handled; + } + + async Task DeferMessage(DelayedRetry action, ErrorContext errorContext) + { + var message = errorContext.Message; + + Logger.Warn($"Delayed Retry will reschedule message '{message.MessageId}' after a delay of {action.Delay} because of an exception:", errorContext.Exception); + + var currentDelayedRetriesAttempts = await delayedRetryExecutor.Retry(message, action.Delay, errorContext.TransportTransaction).ConfigureAwait(false); + + if (raiseNotifications) + { + await eventAggregator.Raise(new MessageToBeRetried(currentDelayedRetriesAttempts, action.Delay, message, errorContext.Exception)).ConfigureAwait(false); + } + return ErrorHandleResult.Handled; + } + + Func recoverabilityPolicy; + IEventAggregator eventAggregator; + DelayedRetryExecutor delayedRetryExecutor; + MoveToErrorsExecutor moveToErrorsExecutor; + bool raiseNotifications; + bool immediateRetriesAvailable; + bool delayedRetriesAvailable; + + static ILog Logger = LogManager.GetLogger(); + RecoverabilityConfig configuration; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityExecutorFactory.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityExecutorFactory.cs new file mode 100644 index 00000000000..2a29acbf22d --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityExecutorFactory.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System; + using Transport; + + class RecoverabilityExecutorFactory + { + public RecoverabilityExecutorFactory(Func defaultRecoverabilityPolicy, RecoverabilityConfig configuration, Func delayedRetryExecutorFactory, + Func moveToErrorsExecutorFactory, bool immediateRetriesAvailable, bool delayedRetriesAvailable) + { + this.configuration = configuration; + this.defaultRecoverabilityPolicy = defaultRecoverabilityPolicy; + this.delayedRetryExecutorFactory = delayedRetryExecutorFactory; + this.moveToErrorsExecutorFactory = moveToErrorsExecutorFactory; + this.immediateRetriesAvailable = immediateRetriesAvailable; + this.delayedRetriesAvailable = delayedRetriesAvailable; + } + + public RecoverabilityExecutor CreateDefault(IEventAggregator eventAggregator, string localAddress) + { + return Create(defaultRecoverabilityPolicy, eventAggregator, localAddress, raiseNotifications: true); + } + + public RecoverabilityExecutor Create(Func customRecoverabilityPolicy, IEventAggregator eventAggregator, string localAddress) + { + return Create(customRecoverabilityPolicy, eventAggregator, localAddress, raiseNotifications: false); + } + + RecoverabilityExecutor Create(Func customRecoverabilityPolicy, IEventAggregator eventAggregator, string localAddress, bool raiseNotifications) + { + var delayedRetryExecutor = delayedRetryExecutorFactory(localAddress); + var moveToErrorsExecutor = moveToErrorsExecutorFactory(localAddress); + + return new RecoverabilityExecutor( + raiseNotifications, + immediateRetriesAvailable, + delayedRetriesAvailable, + customRecoverabilityPolicy, + configuration, + eventAggregator, + delayedRetryExecutor, + moveToErrorsExecutor); + } + + Func defaultRecoverabilityPolicy; + Func delayedRetryExecutorFactory; + Func moveToErrorsExecutorFactory; + readonly bool immediateRetriesAvailable; + readonly bool delayedRetriesAvailable; + RecoverabilityConfig configuration; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Settings/DelayedRetriesSettings.cs b/src/NServiceBus.Core/Recoverability/Settings/DelayedRetriesSettings.cs new file mode 100644 index 00000000000..9ef872a4a3a --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Settings/DelayedRetriesSettings.cs @@ -0,0 +1,40 @@ +namespace NServiceBus +{ + using System; + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// Configuration settings for Delayed Retries. + /// + public class DelayedRetriesSettings : ExposeSettings + { + internal DelayedRetriesSettings(SettingsHolder settings) : base(settings) + { + } + + /// + /// Configures the number of times a message should be retried with a delay after failing Immediate Retries. + /// + public DelayedRetriesSettings NumberOfRetries(int numberOfRetries) + { + Guard.AgainstNegative(nameof(numberOfRetries), numberOfRetries); + + Settings.Set(Recoverability.NumberOfDelayedRetries, numberOfRetries); + + return this; + } + + /// + /// Configures the delay interval increase for each failed Delayed Retries attempt. + /// + public DelayedRetriesSettings TimeIncrease(TimeSpan timeIncrease) + { + Guard.AgainstNegative(nameof(timeIncrease), timeIncrease); + + Settings.Set(Recoverability.DelayedRetriesTimeIncrease, timeIncrease); + + return this; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Settings/ImmediateRetriesSettings.cs b/src/NServiceBus.Core/Recoverability/Settings/ImmediateRetriesSettings.cs new file mode 100644 index 00000000000..96a817ddf02 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Settings/ImmediateRetriesSettings.cs @@ -0,0 +1,27 @@ +namespace NServiceBus +{ + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// Configuration settings for Immediate Retries. + /// + public class ImmediateRetriesSettings : ExposeSettings + { + internal ImmediateRetriesSettings(SettingsHolder settings) : base(settings) + { + } + + /// + /// Configures the amount of times a message should be immediately retried after failing + /// before escalating to Delayed Retries. + /// + /// The number of times to immediately retry a failed message. + public void NumberOfRetries(int numberOfRetries) + { + Guard.AgainstNegative(nameof(numberOfRetries), numberOfRetries); + + Settings.Set(Recoverability.NumberOfImmediateRetries, numberOfRetries); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Settings/RecoverabilityEndpointConfigurationExtensions.cs b/src/NServiceBus.Core/Recoverability/Settings/RecoverabilityEndpointConfigurationExtensions.cs new file mode 100644 index 00000000000..07d1abdbd9f --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Settings/RecoverabilityEndpointConfigurationExtensions.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using Configuration.AdvanceExtensibility; + + /// + /// Extension methods for recoverability which extend . + /// + public static class RecoverabilityEndpointConfigurationExtensions + { + /// + /// Configuration settings for recoverability. + /// + /// The endpoint configuration. + public static RecoverabilitySettings Recoverability(this EndpointConfiguration configuration) + { + return new RecoverabilitySettings(configuration.GetSettings()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Settings/RecoverabilitySettings.cs b/src/NServiceBus.Core/Recoverability/Settings/RecoverabilitySettings.cs new file mode 100644 index 00000000000..2d18dd0efa4 --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Settings/RecoverabilitySettings.cs @@ -0,0 +1,72 @@ +namespace NServiceBus +{ + using System; + using Configuration.AdvanceExtensibility; + using Settings; + using Transport; + + /// + /// Configuration settings for recoverability. + /// + public class RecoverabilitySettings : ExposeSettings + { + internal RecoverabilitySettings(SettingsHolder settings) : base(settings) + { + } + + /// + /// Exposes the retry failed settings. + /// + public RecoverabilitySettings Failed(Action customizations) + { + customizations(new RetryFailedSettings(Settings)); + return this; + } + + /// + /// Exposes the immediate retries settings. + /// + public RecoverabilitySettings Immediate(Action customizations) + { + customizations(new ImmediateRetriesSettings(Settings)); + return this; + } + + /// + /// Exposes the delayed retries settings. + /// + public RecoverabilitySettings Delayed(Action customizations) + { + customizations(new DelayedRetriesSettings(Settings)); + return this; + } + + /// + /// Configures a custom recoverability policy. It allows to take full control over the recoverability decision process. + /// + /// The custom recoverability. + public RecoverabilitySettings CustomPolicy(Func custom) + { + Guard.AgainstNull(nameof(custom), custom); + + Settings.Set(Recoverability.PolicyOverride, custom); + + return this; + } + + /// + /// Disables the legacy retries satellite. The retries satellite is enabled by default to prevent in-flight retry messages from being left + /// in the .Retries queue when migrating from previous versions of NServiceBus. Further details can be found in the V5 to V6 Upgrade Guide. + /// + [ObsoleteEx( + Message = "Legacy retries satellite is no longer needed as of Version 7.", + RemoveInVersion = "8.0", + TreatAsErrorFromVersion = "7.0")] + public RecoverabilitySettings DisableLegacyRetriesSatellite() + { + Settings.Set(Recoverability.DisableLegacyRetriesSatellite, true); + + return this; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Recoverability/Settings/RetryFailedSettings.cs b/src/NServiceBus.Core/Recoverability/Settings/RetryFailedSettings.cs new file mode 100644 index 00000000000..f09e9f869ef --- /dev/null +++ b/src/NServiceBus.Core/Recoverability/Settings/RetryFailedSettings.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// Configuration settings for retry faults. + /// + public class RetryFailedSettings : ExposeSettings + { + internal RetryFailedSettings(SettingsHolder settings) : base(settings) + { + } + + /// + /// Configures a header customization action which gets called after all fault headers have been applied. + /// + /// The customization action. + public RetryFailedSettings HeaderCustomization(Action> customization) + { + Guard.AgainstNull(nameof(customization), customization); + + Settings.Set(Recoverability.FaultHeaderCustomization, customization); + + return this; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ReleaseDateAttribute.cs b/src/NServiceBus.Core/ReleaseDateAttribute.cs new file mode 100644 index 00000000000..f3036610629 --- /dev/null +++ b/src/NServiceBus.Core/ReleaseDateAttribute.cs @@ -0,0 +1,16 @@ +namespace NServiceBus +{ + using System; + + sealed class ReleaseDateAttribute : Attribute + { + public ReleaseDateAttribute(string originalDate, string date) + { + OriginalDate = originalDate; + Date = date; + } + + public string OriginalDate { get; private set; } + public string Date { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/ForceBatchDispatchToBeIsolatedBehavior.cs b/src/NServiceBus.Core/Reliability/Outbox/ForceBatchDispatchToBeIsolatedBehavior.cs new file mode 100644 index 00000000000..3e1e6d97964 --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/ForceBatchDispatchToBeIsolatedBehavior.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class ForceBatchDispatchToBeIsolatedBehavior : IBehavior + { + public Task Invoke(IBatchDispatchContext context, Func next) + { + foreach (var operation in context.Operations) + { + operation.RequiredDispatchConsistency = DispatchConsistency.Isolated; + } + return next(context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/IOutboxStorage.cs b/src/NServiceBus.Core/Reliability/Outbox/IOutboxStorage.cs new file mode 100644 index 00000000000..46004f5b7af --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/IOutboxStorage.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Outbox +{ + using System.Threading.Tasks; + using Extensibility; + + /// + /// Implemented by the persisters to provide outbox storage capabilities. + /// + public interface IOutboxStorage + { + /// + /// Tries to find the given message in the outbox. + /// + /// + /// If there is no present for the given then null is + /// returned. + /// + Task Get(string messageId, ContextBag context); + + /// + /// Stores the outbox message to enable deduplication an re-dispatching of related transport operations. + /// + Task Store(OutboxMessage message, OutboxTransaction transaction, ContextBag context); + + /// + /// Tells the storage that the message has been dispatched and its now safe to clean up the transport operations. + /// + Task SetAsDispatched(string messageId, ContextBag context); + + /// + /// Creates the . + /// + /// The current pipeline contex. + /// The created outbox transaction. + Task BeginTransaction(ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/Outbox.cs b/src/NServiceBus.Core/Reliability/Outbox/Outbox.cs new file mode 100644 index 00000000000..e24165bea8b --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/Outbox.cs @@ -0,0 +1,115 @@ +namespace NServiceBus.Features +{ + using System; + using System.Configuration; + using System.ServiceProcess; + using System.Threading.Tasks; + using ConsistencyGuarantees; + using Logging; + using Persistence; + using Transport; + + /// + /// Configure the Outbox. + /// + public class Outbox : Feature + { + internal Outbox() + { + Defaults(s => s.SetDefault(InMemoryOutboxPersistence.TimeToKeepDeduplicationEntries, TimeSpan.FromDays(5))); + + Prerequisite(c => c.Settings.GetRequiredTransactionModeForReceives() != TransportTransactionMode.None, "Outbox isn't needed since the receive transactions has been turned off"); + + Prerequisite(c => + { + if (!c.Settings.Get().RequireOutboxConsent) + { + return true; + } + + return RequireOutboxConsent(c); + }, "This transport requires outbox consent"); + } + + static bool RequireOutboxConsent(FeatureConfigurationContext context) + { + if (context.Settings.GetOrDefault("DisableOutboxTransportCheck")) + { + return true; + } + var configValue = ConfigurationManager.AppSettings.Get("NServiceBus/Outbox"); + + if (configValue == null) + { + throw new Exception(@"To use the Outbox feature with MSMQ or SQLServer transports it must be enabled in the config file. +To do that add the following: + + + + +The reason this is required is to ensure that all the guidelines regarding this feature have been understood and know the limitations when running under MSMQ or SQLServer transports."); + } + + bool result; + + if (!bool.TryParse(configValue, out result)) + { + throw new Exception("Invalid value in \"NServiceBus/Outbox\" AppSetting. Ensure it is either \"true\" or \"false\"."); + } + + return result; + } + + + /// + /// See . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + if (!PersistenceStartup.HasSupportFor(context.Settings)) + { + throw new Exception("The selected persistence doesn't have support for outbox storage. Select another persistence or disable the outbox feature using endpointConfiguration.DisableFeature()"); + } + + //note: in the future we should change the persister api to give us a "outbox factory" so that we can register it in DI here instead of relying on the persister to do it + + context.RegisterStartupTask(new DtcRunningWarning()); + context.Pipeline.Register("ForceBatchDispatchToBeIsolated", new ForceBatchDispatchToBeIsolatedBehavior(), "Makes sure that we dispatch straight to the transport so that we can safely set the outbox record to dispatched one the dispatch pipeline returns."); + } + } + + class DtcRunningWarning : FeatureStartupTask + { + protected override Task OnStart(IMessageSession session) + { + try + { + var sc = new ServiceController + { + ServiceName = "MSDTC", + MachineName = "." + }; + + if (sc.Status == ServiceControllerStatus.Running) + { + log.Warn(@"The MSDTC service is running on this machine. +Because Outbox is enabled disabling MSDTC is recommended. This ensures that the Outbox behavior is working as expected and no other resources are enlisting in distributed transactions."); + } + } + // ReSharper disable once EmptyGeneralCatchClause + catch (Exception) + { + // Ignore if we can't check it. + } + + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + static ILog log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/OutboxConfigExtensions.cs b/src/NServiceBus.Core/Reliability/Outbox/OutboxConfigExtensions.cs new file mode 100644 index 00000000000..4396348e3e1 --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/OutboxConfigExtensions.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using Outbox; + + /// + /// Config methods for the outbox. + /// + public static class OutboxConfigExtensions + { + /// + /// Enables the outbox feature. + /// + /// The instance to apply the settings to. + public static OutboxSettings EnableOutbox(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + + var outboxSettings = new OutboxSettings(config.Settings); + + config.Settings.SetDefault(TransportTransactionMode.ReceiveOnly); + config.EnableFeature(); + return outboxSettings; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/OutboxMessage.cs b/src/NServiceBus.Core/Reliability/Outbox/OutboxMessage.cs new file mode 100644 index 00000000000..277ce813796 --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/OutboxMessage.cs @@ -0,0 +1,32 @@ +namespace NServiceBus.Outbox +{ + /// + /// The Outbox message type. + /// + public class OutboxMessage + { + /// + /// Creates an instance of an . + /// + /// The message identifier of the incoming message. + /// The outgoing transport operations to execute as part of this incoming message. + public OutboxMessage(string messageId, TransportOperation[] operations) + { + Guard.AgainstNullAndEmpty(nameof(messageId), messageId); + Guard.AgainstNull(nameof(operations), operations); + + MessageId = messageId; + TransportOperations = operations; + } + + /// + /// Gets the message identifier of the incoming message. + /// + public string MessageId { get; private set; } + + /// + /// The list of operations performed during the processing of the incoming message. + /// + public TransportOperation[] TransportOperations { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/OutboxSettings.cs b/src/NServiceBus.Core/Reliability/Outbox/OutboxSettings.cs new file mode 100644 index 00000000000..81d272a4655 --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/OutboxSettings.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Outbox +{ + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// Custom settings related to the outbox feature. + /// + public partial class OutboxSettings : ExposeSettings + { + internal OutboxSettings(SettingsHolder settings) : base(settings) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/OutboxTransaction.cs b/src/NServiceBus.Core/Reliability/Outbox/OutboxTransaction.cs new file mode 100644 index 00000000000..c4ebe376c3e --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/OutboxTransaction.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Outbox +{ + using System; + using System.Threading.Tasks; + + /// + /// Transaction in which storage operations must enlist to be consistent with the outbox operarations. + /// + public interface OutboxTransaction : IDisposable + { + /// + /// Commits the outbox transaction. + /// + Task Commit(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Reliability/Outbox/TransportOperation.cs b/src/NServiceBus.Core/Reliability/Outbox/TransportOperation.cs new file mode 100644 index 00000000000..ec751ca2c6c --- /dev/null +++ b/src/NServiceBus.Core/Reliability/Outbox/TransportOperation.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.Outbox +{ + using System.Collections.Generic; + + /// + /// Outgoing message operation. + /// + public class TransportOperation + { + /// + /// Creates a new instance of a . + /// + /// The identifier of the outgoing message. + /// The sending options. + /// The message body. + /// The message headers. + /// . + public TransportOperation(string messageId, Dictionary options, byte[] body, Dictionary headers) + { + Guard.AgainstNullAndEmpty(nameof(messageId), messageId); + + MessageId = messageId; + Options = options; + Body = body; + Headers = headers; + } + + /// + /// Gets the identifier of the outgoing message. + /// + public string MessageId { get; private set; } + + /// + /// Sending options. + /// + public Dictionary Options { get; private set; } + + /// + /// Gets a byte array to the body content of the outgoing message. + /// + public byte[] Body { get; private set; } + + /// + /// Gets outgoing message headers. + /// + public Dictionary Headers { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/ReplyOptions.cs b/src/NServiceBus.Core/ReplyOptions.cs new file mode 100644 index 00000000000..4efc607f80b --- /dev/null +++ b/src/NServiceBus.Core/ReplyOptions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Exposes options relevant for replies. + /// + public class ReplyOptions : ExtendableOptions + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/AddressTag.cs b/src/NServiceBus.Core/Routing/AddressTag.cs new file mode 100644 index 00000000000..4b6af092a09 --- /dev/null +++ b/src/NServiceBus.Core/Routing/AddressTag.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Routing +{ + /// + /// Represents different ways how the transport should route a given message. + /// + public abstract class AddressTag + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/ApplyReplyToAddressBehavior.cs b/src/NServiceBus.Core/Routing/ApplyReplyToAddressBehavior.cs new file mode 100644 index 00000000000..787e7184356 --- /dev/null +++ b/src/NServiceBus.Core/Routing/ApplyReplyToAddressBehavior.cs @@ -0,0 +1,87 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class ApplyReplyToAddressBehavior : IBehavior + { + public enum RouteOption + { + None, + ExplicitReplyDestination, + RouteReplyToThisInstance, + RouteReplyToAnyInstanceOfThisEndpoint + } + + public ApplyReplyToAddressBehavior(string sharedQueue, string instanceSpecificQueue, string publicReturnAddress, string distributorAddress) + { + this.sharedQueue = sharedQueue; + this.instanceSpecificQueue = instanceSpecificQueue; + this.publicReturnAddress = publicReturnAddress; + this.distributorAddress = distributorAddress; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + var state = context.Extensions.GetOrCreate(); + if (state.Option == RouteOption.RouteReplyToThisInstance && instanceSpecificQueue == null) + { + throw new InvalidOperationException("Cannot route a reply to a specific instance because an endpoint instance discriminator was not configured for the destination endpoint. It can be specified via EndpointConfiguration.MakeInstanceUniquelyAddressable(string discriminator)."); + } + context.Headers[Headers.ReplyToAddress] = ApplyUserOverride(publicReturnAddress ?? sharedQueue, state); + + //Legacy distributor logic + IncomingMessage incomingMessage; + if (context.TryGetIncomingPhysicalMessage(out incomingMessage) && incomingMessage.Headers.ContainsKey(LegacyDistributorHeaders.WorkerSessionId)) + { + context.Headers[Headers.ReplyToAddress] = distributorAddress; + } + return next(context); + } + + + string ApplyUserOverride(string replyTo, State state) + { + if (state.Option == RouteOption.RouteReplyToAnyInstanceOfThisEndpoint) + { + replyTo = sharedQueue; + } + else if (state.Option == RouteOption.RouteReplyToThisInstance) + { + replyTo = instanceSpecificQueue; + } + else if (state.Option == RouteOption.ExplicitReplyDestination) + { + replyTo = state.ExplicitDestination; + } + return replyTo; + } + + string distributorAddress; + string instanceSpecificQueue; + string publicReturnAddress; + + string sharedQueue; + + public class State + { + public RouteOption Option + { + get { return option; } + set + { + if (option != RouteOption.None) + { + throw new Exception("Already specified reply routing option for this message: " + option); + } + option = value; + } + } + + public string ExplicitDestination { get; set; } + RouteOption option; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/AssemblyRouteSource.cs b/src/NServiceBus.Core/Routing/AssemblyRouteSource.cs new file mode 100644 index 00000000000..2feb36de9e3 --- /dev/null +++ b/src/NServiceBus.Core/Routing/AssemblyRouteSource.cs @@ -0,0 +1,37 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Routing; + + class AssemblyRouteSource : IRouteSource + { + Assembly messageAssembly; + UnicastRoute route; + + public AssemblyRouteSource(Assembly messageAssembly, UnicastRoute route) + { + this.messageAssembly = messageAssembly; + this.route = route; + } + + public IEnumerable GenerateRoutes(Conventions conventions) + { + var routes = messageAssembly.GetTypes() + .Where(t => conventions.IsMessageType(t)) + .Select(t => new RouteTableEntry(t, route)) + .ToArray(); + + if (!routes.Any()) + { + throw new Exception($"Cannot configure routing for assembly {messageAssembly.GetName().Name} because it contains no types considered as messages. Message types have to either implement NServiceBus.IMessage interface or match a defined message convention."); + } + + return routes; + } + + public RouteSourcePriority Priority => RouteSourcePriority.Assembly; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribe.cs b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribe.cs new file mode 100644 index 00000000000..261094e1617 --- /dev/null +++ b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribe.cs @@ -0,0 +1,96 @@ +namespace NServiceBus.Features +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Logging; + using Routing.MessageDrivenSubscriptions; + using Transport; + using Unicast; + + /// + /// Used to configure auto subscriptions. + /// + public class AutoSubscribe : Feature + { + internal AutoSubscribe() + { + EnableByDefault(); + Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"), "Send only endpoints can't autosubscribe."); + } + + /// + /// See . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + SubscribeSettings settings; + + if (!context.Settings.TryGet(out settings)) + { + settings = new SubscribeSettings(); + } + + var conventions = context.Settings.Get(); + var transportInfrastructure = context.Settings.Get(); + var requireExplicitRouting = transportInfrastructure.OutboundRoutingPolicy.Publishes == OutboundRoutingType.Unicast; + var publishers = context.Settings.Get(); + + context.RegisterStartupTask(b => + { + var handlerRegistry = b.Build(); + var messageTypesHandled = GetMessageTypesHandledByThisEndpoint(handlerRegistry, conventions, settings); + var typesToSubscribe = messageTypesHandled.Where(eventType => !requireExplicitRouting || publishers.GetPublisherFor(eventType).Any()); + return new ApplySubscriptions(typesToSubscribe); + }); + } + + static List GetMessageTypesHandledByThisEndpoint(MessageHandlerRegistry handlerRegistry, Conventions conventions, SubscribeSettings settings) + { + var messageTypesHandled = handlerRegistry.GetMessageTypes() //get all potential messages + .Where(t => !conventions.IsInSystemConventionList(t)) //never auto-subscribe system messages + .Where(t => !conventions.IsCommandType(t)) //commands should never be subscribed to + .Where(conventions.IsEventType) //only events unless the user asked for all messages + .Where(t => settings.AutoSubscribeSagas || handlerRegistry.GetHandlersFor(t).Any(handler => !typeof(Saga).IsAssignableFrom(handler.HandlerType))) //get messages with other handlers than sagas if needed + .ToList(); + + return messageTypesHandled; + } + + class ApplySubscriptions : FeatureStartupTask + { + public ApplySubscriptions(IEnumerable messagesHandledByThisEndpoint) + { + this.messagesHandledByThisEndpoint = messagesHandledByThisEndpoint; + } + + protected override async Task OnStart(IMessageSession session) + { + foreach (var eventType in messagesHandledByThisEndpoint) + { + await session.Subscribe(eventType).ConfigureAwait(false); + Logger.DebugFormat("Auto subscribed to event {0}", eventType); + } + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + IEnumerable messagesHandledByThisEndpoint; + static ILog Logger = LogManager.GetLogger(); + } + + internal class SubscribeSettings + { + public SubscribeSettings() + { + AutoSubscribeSagas = true; + } + + public bool AutoSubscribeSagas { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettings.cs b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettings.cs new file mode 100644 index 00000000000..d69e317605f --- /dev/null +++ b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettings.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.AutomaticSubscriptions.Config +{ + using Features; + + /// + /// Provides fine grained control over auto subscribe. + /// + public partial class AutoSubscribeSettings + { + internal AutoSubscribeSettings(EndpointConfiguration config) + { + this.config = config; + } + + /// + /// Turns off auto subscriptions for sagas. Sagas where not auto subscribed by default before v4. + /// + public void DoNotAutoSubscribeSagas() + { + GetSettings().AutoSubscribeSagas = false; + } + + AutoSubscribe.SubscribeSettings GetSettings() + { + AutoSubscribe.SubscribeSettings settings; + + if (!config.Settings.TryGet(out settings)) + { + settings = new AutoSubscribe.SubscribeSettings(); + config.Settings.Set(settings); + } + return settings; + } + + EndpointConfiguration config; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettingsExtensions.cs b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettingsExtensions.cs new file mode 100644 index 00000000000..4713ef0a9eb --- /dev/null +++ b/src/NServiceBus.Core/Routing/AutomaticSubscriptions/AutoSubscribeSettingsExtensions.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using AutomaticSubscriptions.Config; + + /// + /// Adds support for custom configuration of the auto subscribe feature. + /// + public static class AutoSubscribeSettingsExtensions + { + /// + /// Use this method to change how auto subscribe works. + /// + /// The instance to apply the settings to. + public static AutoSubscribeSettings AutoSubscribe(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + return new AutoSubscribeSettings(config); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/ConfiguredPublishers.cs b/src/NServiceBus.Core/Routing/ConfiguredPublishers.cs new file mode 100644 index 00000000000..ff205d081d0 --- /dev/null +++ b/src/NServiceBus.Core/Routing/ConfiguredPublishers.cs @@ -0,0 +1,30 @@ +namespace NServiceBus.Features +{ + using System.Collections.Generic; + using System.Linq; + using Routing.MessageDrivenSubscriptions; + + class ConfiguredPublishers + { + List publisherSources = new List(); + + public void Add(IPublisherSource publisherSource) + { + Guard.AgainstNull(nameof(publisherSource), publisherSource); + publisherSources.Add(publisherSource); + } + + public void Apply(Publishers publishers, Conventions conventions, bool enforceBestPractices) + { + var entries = publisherSources.SelectMany(s => Generate(conventions, s, enforceBestPractices)).ToList(); + publishers.AddOrReplacePublishers("EndpointConfiguration", entries); + } + + static IEnumerable Generate(Conventions conventions, IPublisherSource source, bool enforceBestPractices) + { + return enforceBestPractices + ? source.GenerateWithBestPracticeEnforcement(conventions) + : source.GenerateWithoutBestPracticeEnforcement(conventions); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/ConfiguredUnicastRoutes.cs b/src/NServiceBus.Core/Routing/ConfiguredUnicastRoutes.cs new file mode 100644 index 00000000000..45b73d3735b --- /dev/null +++ b/src/NServiceBus.Core/Routing/ConfiguredUnicastRoutes.cs @@ -0,0 +1,31 @@ +namespace NServiceBus.Features +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Routing; + + class ConfiguredUnicastRoutes + { + List routeSources = new List(); + + public void Add(IRouteSource routeSource) + { + Guard.AgainstNull(nameof(routeSource), routeSource); + routeSources.Add(routeSource); + } + + public void Apply(UnicastRoutingTable unicastRoutingTable, Conventions conventions) + { + var entries = new Dictionary(); + foreach (var source in routeSources.OrderBy(x => x.Priority)) //Higher priority routes sources override lower priority. + { + foreach (var route in source.GenerateRoutes(conventions)) + { + entries[route.MessageType] = route; + } + } + unicastRoutingTable.AddOrReplaceRoutes("EndpointConfiguration", entries.Values.ToList()); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/DistributionPolicy.cs b/src/NServiceBus.Core/Routing/DistributionPolicy.cs new file mode 100644 index 00000000000..573786bb725 --- /dev/null +++ b/src/NServiceBus.Core/Routing/DistributionPolicy.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Concurrent; + using Routing; + + /// + /// Configures distribution strategies. + /// + public class DistributionPolicy : IDistributionPolicy + { + /// + /// Sets the distribution strategy for a given endpoint. + /// + /// Distribution strategy to be used. + public void SetDistributionStrategy(DistributionStrategy distributionStrategy) + { + Guard.AgainstNull(nameof(distributionStrategy), distributionStrategy); + + configuredStrategies[Tuple.Create(distributionStrategy.Endpoint, distributionStrategy.Scope)] = distributionStrategy; + } + + DistributionStrategy IDistributionPolicy.GetDistributionStrategy(string endpointName, DistributionStrategyScope scope) + { + return configuredStrategies.GetOrAdd(Tuple.Create(endpointName, scope), key => new SingleInstanceRoundRobinDistributionStrategy(key.Item1, key.Item2)); + } + + ConcurrentDictionary, DistributionStrategy> configuredStrategies = new ConcurrentDictionary, DistributionStrategy>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/DistributionStrategy.cs b/src/NServiceBus.Core/Routing/DistributionStrategy.cs new file mode 100644 index 00000000000..b8f971c6f1b --- /dev/null +++ b/src/NServiceBus.Core/Routing/DistributionStrategy.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Routing +{ + /// + /// Determines which instance of a given endpoint a message is to be sent. + /// + public abstract class DistributionStrategy + { + /// + /// Creates a new . + /// + /// The name of the endpoint this distribution strategy resolves instances for. + /// The scope for this strategy. + protected DistributionStrategy(string endpoint, DistributionStrategyScope scope) + { + Guard.AgainstNullAndEmpty(nameof(endpoint), endpoint); + + Endpoint = endpoint; + Scope = scope; + } + + /// + /// Selects a destination instance for a message from all known addresses of a logical endpoint. + /// + public abstract string SelectReceiver(string[] receiverAddresses); + + /// + /// The name of the endpoint this distribution strategy resolves instances for. + /// + public string Endpoint { get; } + + /// + /// The scope of this strategy. + /// + public DistributionStrategyScope Scope { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/DistributionStrategyScope.cs b/src/NServiceBus.Core/Routing/DistributionStrategyScope.cs new file mode 100644 index 00000000000..2d6e2c22d9a --- /dev/null +++ b/src/NServiceBus.Core/Routing/DistributionStrategyScope.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using Routing; + + /// + /// Defines the usage scope of a . + /// + public enum DistributionStrategyScope + { + + /// + /// All outgoing messages and commands, excluding events and subscription messages. + /// + Send, + + /// + /// All published events. + /// + Publish + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/EndpointInstance.cs b/src/NServiceBus.Core/Routing/EndpointInstance.cs new file mode 100644 index 00000000000..561e1fc0c4e --- /dev/null +++ b/src/NServiceBus.Core/Routing/EndpointInstance.cs @@ -0,0 +1,175 @@ +namespace NServiceBus.Routing +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents a name of an endpoint instance. + /// + public sealed class EndpointInstance + { + /// + /// Creates a new endpoint name for a given discriminator. + /// + /// The name of the endpoint. + /// A specific discriminator for scale-out purposes. + /// A bag of additional properties that differentiate this endpoint instance from other instances. + public EndpointInstance(string endpoint, string discriminator = null, IReadOnlyDictionary properties = null) + { + Guard.AgainstNull(nameof(endpoint), endpoint); + + Properties = properties ?? new Dictionary(); + Endpoint = endpoint; + Discriminator = discriminator; + } + + /// + /// Returns the name of the endpoint. + /// + public string Endpoint { get; } + + /// + /// A specific discriminator for scale-out purposes. + /// + public string Discriminator { get; } + + /// + /// Returns all the differentiating properties of this instance. + /// + public IReadOnlyDictionary Properties { get; } + + /// + /// Sets a property for an endpoint instance returning a new instance with the given property set. + /// + /// Key. + /// Value. + public EndpointInstance SetProperty(string key, string value) + { + Guard.AgainstNull(nameof(key), key); + var newProperties = new Dictionary(); + foreach (var property in Properties) + { + newProperties[property.Key] = property.Value; + } + newProperties[key] = value; + return new EndpointInstance(Endpoint, Discriminator, newProperties); + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + var propsFormatted = Properties.Select(kvp => $"{kvp.Key}:{kvp.Value}"); + var instanceId = Discriminator != null + ? $"{Endpoint}-{Discriminator}" + : Endpoint; + + var parts = new[] + { + instanceId + }.Concat(propsFormatted); + return string.Join(";", parts); + } + + bool Equals(EndpointInstance other) + { + return PropertiesEqual(Properties, other.Properties) + && Equals(Endpoint, other.Endpoint) + && string.Equals(Discriminator, other.Discriminator); + } + + static bool PropertiesEqual(IReadOnlyDictionary left, IReadOnlyDictionary right) + { + foreach (var p in left) + { + string equivalent; + if (!right.TryGetValue(p.Key, out equivalent)) + { + return false; + } + if (p.Value != equivalent) + { + return false; + } + } + return true; + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + return obj is EndpointInstance && Equals((EndpointInstance) obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Properties.Aggregate(Endpoint.GetHashCode(), (i, pair) => (i*397) ^ propertyComparer.GetHashCode(pair)); + hashCode = (hashCode*397) ^ (Discriminator?.GetHashCode() ?? 0); + return hashCode; + } + } + + /// + /// Checks for equality. + /// + public static bool operator ==(EndpointInstance left, EndpointInstance right) + { + return Equals(left, right); + } + + /// + /// Checks for inequality. + /// + public static bool operator !=(EndpointInstance left, EndpointInstance right) + { + return !Equals(left, right); + } + + static readonly IEqualityComparer> propertyComparer = new PropertyComparer(); + + class PropertyComparer : IEqualityComparer> + { + public bool Equals(KeyValuePair x, KeyValuePair y) + { + return Equals(x.Key, y.Key) + && Equals(x.Value, y.Value); + } + + public int GetHashCode(KeyValuePair obj) + { + var hashCode = obj.Key.GetHashCode(); + if (obj.Value != null) + { + hashCode ^= 397*obj.Value.GetHashCode(); + } + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/EndpointInstances.cs b/src/NServiceBus.Core/Routing/EndpointInstances.cs new file mode 100644 index 00000000000..991d5fc5ab4 --- /dev/null +++ b/src/NServiceBus.Core/Routing/EndpointInstances.cs @@ -0,0 +1,56 @@ +namespace NServiceBus.Routing +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Stores the information about instances of known endpoints. + /// + public class EndpointInstances + { + internal IEnumerable FindInstances(string endpoint) + { + HashSet registeredInstances; + return allInstances.TryGetValue(endpoint, out registeredInstances) + ? registeredInstances + : DefaultInstance(endpoint); + } + + static IEnumerable DefaultInstance(string endpoint) + { + yield return new EndpointInstance(endpoint); + } + + /// + /// Adds or replaces a set of endpoint instances registered under a given key (registration source ID). + /// + /// Source key. + /// List of endpoint instances known by this source. + public void AddOrReplaceInstances(string sourceKey, IList endpointInstances) + { + Guard.AgainstNull(nameof(sourceKey), sourceKey); + Guard.AgainstNull(nameof(endpointInstances), endpointInstances); + lock (updateLock) + { + registrations[sourceKey] = endpointInstances; + var newCache = new Dictionary>(); + + foreach (var instance in registrations.Values.SelectMany(x => x)) + { + HashSet instanceSet; + if (!newCache.TryGetValue(instance.Endpoint, out instanceSet)) + { + instanceSet = new HashSet(); + newCache[instance.Endpoint] = instanceSet; + } + instanceSet.Add(instance); + } + allInstances = newCache; + } + } + + Dictionary> allInstances = new Dictionary>(); + Dictionary> registrations = new Dictionary>(); + object updateLock = new object(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/IDistributionPolicy.cs b/src/NServiceBus.Core/Routing/IDistributionPolicy.cs new file mode 100644 index 00000000000..31a36915fed --- /dev/null +++ b/src/NServiceBus.Core/Routing/IDistributionPolicy.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + using Routing; + + interface IDistributionPolicy + { + DistributionStrategy GetDistributionStrategy(string endpointName, DistributionStrategyScope scope); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/IRouteSource.cs b/src/NServiceBus.Core/Routing/IRouteSource.cs new file mode 100644 index 00000000000..4f90e4c8ce0 --- /dev/null +++ b/src/NServiceBus.Core/Routing/IRouteSource.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Routing; + + interface IRouteSource + { + IEnumerable GenerateRoutes(Conventions conventions); + RouteSourcePriority Priority { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/ISubscribeContext.cs b/src/NServiceBus.Core/Routing/ISubscribeContext.cs new file mode 100644 index 00000000000..ea13c4a99e4 --- /dev/null +++ b/src/NServiceBus.Core/Routing/ISubscribeContext.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Pipeline +{ + using System; + + /// + /// Provides context for subscription requests. + /// + public interface ISubscribeContext : IBehaviorContext + { + /// + /// The type of the event. + /// + Type EventType { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/IUnicastPublishRouter.cs b/src/NServiceBus.Core/Routing/IUnicastPublishRouter.cs new file mode 100644 index 00000000000..7170d8a0049 --- /dev/null +++ b/src/NServiceBus.Core/Routing/IUnicastPublishRouter.cs @@ -0,0 +1,13 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Routing; + + interface IUnicastPublishRouter + { + Task> Route(Type messageType, IDistributionPolicy distributionPolicy, ContextBag contextBag); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/IUnicastSendRouter.cs b/src/NServiceBus.Core/Routing/IUnicastSendRouter.cs new file mode 100644 index 00000000000..2701031f534 --- /dev/null +++ b/src/NServiceBus.Core/Routing/IUnicastSendRouter.cs @@ -0,0 +1,10 @@ +namespace NServiceBus +{ + using System; + using Routing; + + interface IUnicastSendRouter + { + UnicastRoutingStrategy Route(Type messageType, IDistributionPolicy distributionPolicy); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/IUnsubscribeContext.cs b/src/NServiceBus.Core/Routing/IUnsubscribeContext.cs new file mode 100644 index 00000000000..b1824d91d4a --- /dev/null +++ b/src/NServiceBus.Core/Routing/IUnsubscribeContext.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Pipeline +{ + using System; + + /// + /// Provides context for unsubscribe requests. + /// + public interface IUnsubscribeContext : IBehaviorContext + { + /// + /// The type of the event. + /// + Type EventType { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Legacy/ConfigureMSMQDistributor.cs b/src/NServiceBus.Core/Routing/Legacy/ConfigureMSMQDistributor.cs new file mode 100644 index 00000000000..b02cc6ac6bd --- /dev/null +++ b/src/NServiceBus.Core/Routing/Legacy/ConfigureMSMQDistributor.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Routing.Legacy +{ + using System; + using Configuration.AdvanceExtensibility; + + /// + /// Extension methods to configure Distributor. + /// + public static class ConfigureMSMQDistributor + { + /// + /// Enlist Worker with Master node defined in the config. + /// + public static void EnlistWithLegacyMSMQDistributor(this EndpointConfiguration config, string masterNodeAddress, string masterNodeControlAddress, int capacity) + { + if (masterNodeAddress == null) + { + throw new ArgumentNullException(nameof(masterNodeAddress)); + } + config.GetSettings().Set("LegacyDistributor.Address", masterNodeAddress); + config.GetSettings().Set("LegacyDistributor.ControlAddress", masterNodeControlAddress); + config.GetSettings().Set("LegacyDistributor.Capacity", capacity); + config.EnableFeature(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Legacy/LegacyDistributorHeaders.cs b/src/NServiceBus.Core/Routing/Legacy/LegacyDistributorHeaders.cs new file mode 100644 index 00000000000..3af6ff31b42 --- /dev/null +++ b/src/NServiceBus.Core/Routing/Legacy/LegacyDistributorHeaders.cs @@ -0,0 +1,10 @@ +namespace NServiceBus +{ + static class LegacyDistributorHeaders + { + public const string WorkerCapacityAvailable = "NServiceBus.Distributor.WorkerCapacityAvailable"; + public const string WorkerStarting = "NServiceBus.Distributor.WorkerStarting"; + public const string UnregisterWorker = "NServiceBus.Distributor.UnregisterWorker"; + public const string WorkerSessionId = "NServiceBus.Distributor.WorkerSessionId"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Legacy/ProcessedMessageCounterBehavior.cs b/src/NServiceBus.Core/Routing/Legacy/ProcessedMessageCounterBehavior.cs new file mode 100644 index 00000000000..c9f913f649a --- /dev/null +++ b/src/NServiceBus.Core/Routing/Legacy/ProcessedMessageCounterBehavior.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class ProcessedMessageCounterBehavior : IBehavior + { + public ProcessedMessageCounterBehavior(ReadyMessageSender readyMessageSender) + { + this.readyMessageSender = readyMessageSender; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + await next(context).ConfigureAwait(false); + await readyMessageSender.MessageProcessed(context.Message.Headers).ConfigureAwait(false); + } + + ReadyMessageSender readyMessageSender; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Legacy/ReadyMessageSender.cs b/src/NServiceBus.Core/Routing/Legacy/ReadyMessageSender.cs new file mode 100644 index 00000000000..a45ca3a76c6 --- /dev/null +++ b/src/NServiceBus.Core/Routing/Legacy/ReadyMessageSender.cs @@ -0,0 +1,81 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Features; + using Logging; + using Routing; + using Transport; + using Unicast.Transport; + + class ReadyMessageSender : FeatureStartupTask + { + public ReadyMessageSender(IDispatchMessages dispatcher, string receiveAddress, int initialCapacity, string distributorControlAddress) + { + this.initialCapacity = initialCapacity; + this.distributorControlAddress = distributorControlAddress; + this.dispatcher = dispatcher; + this.receiveAddress = receiveAddress; + } + + protected override Task OnStart(IMessageSession session) + { + Logger.DebugFormat("Sending ready startup message with WorkerSessionId {0} sent. ", workerSessionId); + + return SendReadyMessage(initialCapacity, true); + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + Task SendReadyMessage(int capacity, bool isStarting) + { + //we use the actual address to make sure that the worker inside the master node will check in correctly + var readyMessage = ControlMessageFactory.Create(MessageIntentEnum.Send); + + readyMessage.Headers.Add(LegacyDistributorHeaders.WorkerCapacityAvailable, capacity.ToString()); + readyMessage.Headers.Add(LegacyDistributorHeaders.WorkerSessionId, workerSessionId); + readyMessage.Headers.Add(Headers.ReplyToAddress, receiveAddress); + + if (isStarting) + { + readyMessage.Headers.Add(LegacyDistributorHeaders.WorkerStarting, bool.TrueString); + } + + var transportOperation = new TransportOperation(readyMessage, new UnicastAddressTag(distributorControlAddress)); + return dispatcher.Dispatch(new TransportOperations(transportOperation), new TransportTransaction(), new ContextBag()); + } + + public Task MessageProcessed(Dictionary headers) + { + //if there was a failure this "send" will be rolled back + string messageSessionId; + headers.TryGetValue(LegacyDistributorHeaders.WorkerSessionId, out messageSessionId); + if (messageSessionId == null) + { + return TaskEx.CompletedTask; + } + var messageId = headers[Headers.MessageId]; + Logger.DebugFormat("Got message with id {0} and messageSessionId {1}. WorkerSessionId is {2}", messageId, messageSessionId, workerSessionId); + if (messageSessionId != workerSessionId) + { + Logger.InfoFormat("SKIPPED Ready message for message with id {0} because of sessionId mismatch. MessageSessionId {1}, WorkerSessionId {2}", messageId, messageSessionId, workerSessionId); + return TaskEx.CompletedTask; + } + + return SendReadyMessage(1, false); + } + + readonly string receiveAddress; + IDispatchMessages dispatcher; + + string distributorControlAddress; + int initialCapacity; + string workerSessionId = Guid.NewGuid().ToString(); + static readonly ILog Logger = LogManager.GetLogger(typeof(ReadyMessageSender)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Legacy/WorkerFeature.cs b/src/NServiceBus.Core/Routing/Legacy/WorkerFeature.cs new file mode 100644 index 00000000000..b859c0f62fc --- /dev/null +++ b/src/NServiceBus.Core/Routing/Legacy/WorkerFeature.cs @@ -0,0 +1,20 @@ +namespace NServiceBus +{ + using Features; + using Transport; + + class WorkerFeature : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) + { + var masterNodeControlAddress = context.Settings.Get("LegacyDistributor.ControlAddress"); + var capacity = context.Settings.Get("LegacyDistributor.Capacity"); + + context.Container.ConfigureComponent(b => new ReadyMessageSender(b.Build(), context.Settings.LocalAddress(), capacity, masterNodeControlAddress), DependencyLifecycle.SingleInstance); + context.Container.ConfigureComponent(b => new ProcessedMessageCounterBehavior(b.Build()), DependencyLifecycle.SingleInstance); + + context.RegisterStartupTask(b => b.Build()); + context.Pipeline.Register("ProcessedMessageCounterBehavior", b => b.Build(), "Counts messages processed by the worker."); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/AssemblyPublisherSource.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/AssemblyPublisherSource.cs new file mode 100644 index 00000000000..9a763e4473e --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/AssemblyPublisherSource.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Routing.MessageDrivenSubscriptions; + + class AssemblyPublisherSource : IPublisherSource + { + Assembly messageAssembly; + PublisherAddress address; + + public AssemblyPublisherSource(Assembly messageAssembly, PublisherAddress address) + { + this.messageAssembly = messageAssembly; + this.address = address; + } + + public IEnumerable GenerateWithBestPracticeEnforcement(Conventions conventions) + { + var entries = messageAssembly.GetTypes() + .Where(conventions.IsEventType) + .Select(t => new PublisherTableEntry(t, address)) + .ToArray(); + + if (!entries.Any()) + { + throw new Exception($"Cannot configure publisher for assembly {messageAssembly.GetName().Name} because it contains no types considered as events. Event types have to either implement NServiceBus.IEvent interface or match a defined event convention."); + } + + return entries; + } + + public IEnumerable GenerateWithoutBestPracticeEnforcement(Conventions conventions) + { + var entries = messageAssembly.GetTypes() + .Where(type => conventions.IsMessageType(type) && !conventions.IsCommandType(type)) + .Select(t => new PublisherTableEntry(t, address)) + .ToArray(); + + if (!entries.Any()) + { + throw new Exception($"Cannot configure publisher for assembly {messageAssembly.GetName().Name} because it contains no types considered as messages. Message types have to either implement NServiceBus.IMessage interface or match a defined convention."); + } + + return entries; + } + + public RouteSourcePriority Priority => RouteSourcePriority.Assembly; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IInitializableSubscriptionStorage.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IInitializableSubscriptionStorage.cs new file mode 100644 index 00000000000..8df974c96fc --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IInitializableSubscriptionStorage.cs @@ -0,0 +1,14 @@ +namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions +{ + /// + /// Defines an initializable storage for subscriptions. + /// + public interface IInitializableSubscriptionStorage : ISubscriptionStorage + { + /// + /// Notifies the subscription storage that now is the time to perform + /// any initialization work. + /// + void Init(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IMessageDrivenSubscriptionTransport.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IMessageDrivenSubscriptionTransport.cs new file mode 100644 index 00000000000..4ff9770b5b1 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IMessageDrivenSubscriptionTransport.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Routing +{ + /// + /// Marks the transport as using message-driven pub/sub. + /// + public interface IMessageDrivenSubscriptionTransport + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IPublisherSource.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IPublisherSource.cs new file mode 100644 index 00000000000..3bd3d31684b --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/IPublisherSource.cs @@ -0,0 +1,12 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using Routing.MessageDrivenSubscriptions; + + interface IPublisherSource + { + IEnumerable GenerateWithBestPracticeEnforcement(Conventions conventions); + IEnumerable GenerateWithoutBestPracticeEnforcement(Conventions conventions); + RouteSourcePriority Priority { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/ISubscriptionStorage.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/ISubscriptionStorage.cs new file mode 100644 index 00000000000..6d5aa965842 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/ISubscriptionStorage.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + + /// + /// Defines storage for subscriptions. + /// + public interface ISubscriptionStorage + { + /// + /// Subscribes the given client to messages of a given type. + /// + Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context); + + /// + /// Unsubscribes the given client from messages of given type. + /// + Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context); + + /// + /// Returns a list of addresses for subscribers currently subscribed to the given message type. + /// + Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscribeTerminator.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscribeTerminator.cs new file mode 100644 index 00000000000..b8d41936584 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscribeTerminator.cs @@ -0,0 +1,95 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Logging; + using Pipeline; + using Routing; + using Transport; + using Unicast.Queuing; + using Unicast.Transport; + + class MessageDrivenSubscribeTerminator : PipelineTerminator + { + public MessageDrivenSubscribeTerminator(SubscriptionRouter subscriptionRouter, string subscriberAddress, string subscriberEndpoint, IDispatchMessages dispatcher) + { + this.subscriptionRouter = subscriptionRouter; + this.subscriberAddress = subscriberAddress; + this.subscriberEndpoint = subscriberEndpoint; + this.dispatcher = dispatcher; + } + + protected override async Task Terminate(ISubscribeContext context) + { + var eventType = context.EventType; + + var publisherAddresses = subscriptionRouter.GetAddressesForEventType(eventType) + .EnsureNonEmpty(() => $"No publisher address could be found for message type {eventType}. Ensure the configured publisher endpoint has at least one known instance."); + + var subscribeTasks = new List(); + foreach (var publisherAddress in publisherAddresses) + { + Logger.Debug($"Subscribing to {eventType.AssemblyQualifiedName} at publisher queue {publisherAddress}"); + + var subscriptionMessage = ControlMessageFactory.Create(MessageIntentEnum.Subscribe); + + subscriptionMessage.Headers[Headers.SubscriptionMessageType] = eventType.AssemblyQualifiedName; + subscriptionMessage.Headers[Headers.ReplyToAddress] = subscriberAddress; + subscriptionMessage.Headers[Headers.SubscriberTransportAddress] = subscriberAddress; + subscriptionMessage.Headers[Headers.SubscriberEndpoint] = subscriberEndpoint; + subscriptionMessage.Headers[Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); + subscriptionMessage.Headers[Headers.NServiceBusVersion] = GitFlowVersion.MajorMinorPatch; + + subscribeTasks.Add(SendSubscribeMessageWithRetries(publisherAddress, subscriptionMessage, eventType.AssemblyQualifiedName, context.Extensions)); + } + await Task.WhenAll(subscribeTasks.ToArray()).ConfigureAwait(false); + } + + async Task SendSubscribeMessageWithRetries(string destination, OutgoingMessage subscriptionMessage, string messageType, ContextBag context, int retriesCount = 0) + { + var state = context.GetOrCreate(); + try + { + var transportOperation = new TransportOperation(subscriptionMessage, new UnicastAddressTag(destination)); + var transportTransaction = context.GetOrCreate(); + await dispatcher.Dispatch(new TransportOperations(transportOperation), transportTransaction, context).ConfigureAwait(false); + } + catch (QueueNotFoundException ex) + { + if (retriesCount < state.MaxRetries) + { + await Task.Delay(state.RetryDelay).ConfigureAwait(false); + await SendSubscribeMessageWithRetries(destination, subscriptionMessage, messageType, context, ++retriesCount).ConfigureAwait(false); + } + else + { + string message = $"Failed to subscribe to {messageType} at publisher queue {destination}, reason {ex.Message}"; + Logger.Error(message, ex); + throw new QueueNotFoundException(destination, message, ex); + } + } + } + + IDispatchMessages dispatcher; + string subscriberAddress; + string subscriberEndpoint; + + SubscriptionRouter subscriptionRouter; + + static ILog Logger = LogManager.GetLogger(); + + public class Settings + { + public Settings() + { + MaxRetries = 10; + RetryDelay = TimeSpan.FromSeconds(2); + } + + public TimeSpan RetryDelay { get; set; } + public int MaxRetries { get; set; } + } + } +} diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs new file mode 100644 index 00000000000..ef1b97e2482 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Features +{ + using System; + using Persistence; + using Transport; + + /// + /// Allows subscribers to register by sending a subscription message to this endpoint. + /// + public class MessageDrivenSubscriptions : Feature + { + internal MessageDrivenSubscriptions() + { + EnableByDefault(); + Prerequisite(c => c.Settings.Get().OutboundRoutingPolicy.Publishes == OutboundRoutingType.Unicast, "The transport supports native pub sub"); + } + + /// + /// See . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + if (!PersistenceStartup.HasSupportFor(context.Settings)) + { + throw new Exception("The selected persistence doesn't have support for subscription storage. Select another persistence or disable the message-driven subscriptions feature using endpointConfiguration.DisableFeature()"); + } + + context.Pipeline.Register(); + var authorizer = context.Settings.GetSubscriptionAuthorizer(); + if (authorizer == null) + { + authorizer = _ => true; + } + context.Container.RegisterSingleton(authorizer); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensions.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensions.cs new file mode 100644 index 00000000000..7225aa67706 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenSubscriptionsConfigExtensions.cs @@ -0,0 +1,96 @@ +namespace NServiceBus +{ + using System; + using System.Reflection; + using Features; + using Pipeline; + using Routing; + using Routing.MessageDrivenSubscriptions; + using Settings; + using Transport; + + /// + /// Provides extensions for configuring message driven subscriptions. + /// + public static class MessageDrivenSubscriptionsConfigExtensions + { + /// + /// Sets an authorizer to be used when processing a or + /// message. + /// + /// The to extend. + /// The authorization callback to execute. If the callback returns true for a message, it is authorized to subscribe/unsubscribe, otherwhise it is not authorized. + public static void SubscriptionAuthorizer(this TransportExtensions transportExtensions, Func authorizer) where T : TransportDefinition, IMessageDrivenSubscriptionTransport + { + Guard.AgainstNull(nameof(authorizer), authorizer); + var settings = transportExtensions.Settings; + + settings.Set("SubscriptionAuthorizer", authorizer); + } + + internal static Func GetSubscriptionAuthorizer(this ReadOnlySettings settings) + { + Func authorizer; + settings.TryGet("SubscriptionAuthorizer", out authorizer); + return authorizer; + } + + /// + /// Registers a publisher endpoint for a given event type. + /// + /// The to extend. + /// The event type. + /// The publisher endpoint. + public static void RegisterPublisher(this RoutingSettings routingSettings, Type eventType, string publisherEndpoint) where T : TransportDefinition, IMessageDrivenSubscriptionTransport + { + Guard.AgainstNullAndEmpty(nameof(publisherEndpoint), publisherEndpoint); + + ThrowOnAddress(publisherEndpoint); + routingSettings.Settings.GetOrCreate().Add(new TypePublisherSource(eventType, PublisherAddress.CreateFromEndpointName(publisherEndpoint))); + } + + /// + /// Registers a publisher endpoint for all event types in a given assembly. + /// + /// The to extend. + /// The assembly containing the event types. + /// The publisher endpoint. + public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string publisherEndpoint) where T : TransportDefinition, IMessageDrivenSubscriptionTransport + { + Guard.AgainstNull(nameof(assembly), assembly); + Guard.AgainstNullAndEmpty(nameof(publisherEndpoint), publisherEndpoint); + + ThrowOnAddress(publisherEndpoint); + + routingSettings.Settings.GetOrCreate().Add(new AssemblyPublisherSource(assembly, PublisherAddress.CreateFromEndpointName(publisherEndpoint))); + } + + /// + /// Registers a publisher endpoint for all event types in a given assembly and namespace. + /// + /// The to extend. + /// The assembly containing the event types. + /// The namespace containing the event types. The given value must exactly match the target namespace. + /// The publisher endpoint. + public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string @namespace, string publisherEndpoint) where T : TransportDefinition, IMessageDrivenSubscriptionTransport + { + Guard.AgainstNull(nameof(assembly), assembly); + Guard.AgainstNullAndEmpty(nameof(publisherEndpoint), publisherEndpoint); + + ThrowOnAddress(publisherEndpoint); + + // empty namespace is null, not string.empty + @namespace = @namespace == string.Empty ? null : @namespace; + + routingSettings.Settings.GetOrCreate().Add(new NamespacePublisherSource(assembly, @namespace, PublisherAddress.CreateFromEndpointName(publisherEndpoint))); + } + + static void ThrowOnAddress(string publisherEndpoint) + { + if (publisherEndpoint.Contains("@")) + { + throw new ArgumentException($"A logical endpoint name should not contain '@', but received '{publisherEndpoint}'. To specify an endpoint's address, use the instance mapping file for the MSMQ transport, or refer to the routing documentation."); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenUnsubscribeTerminator.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenUnsubscribeTerminator.cs new file mode 100644 index 00000000000..19e91158c87 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageDrivenUnsubscribeTerminator.cs @@ -0,0 +1,95 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using Logging; + using Pipeline; + using Routing; + using Transport; + using Unicast.Queuing; + using Unicast.Transport; + + class MessageDrivenUnsubscribeTerminator : PipelineTerminator + { + public MessageDrivenUnsubscribeTerminator(SubscriptionRouter subscriptionRouter, string replyToAddress, string endpoint, IDispatchMessages dispatcher) + { + this.subscriptionRouter = subscriptionRouter; + this.replyToAddress = replyToAddress; + this.endpoint = endpoint; + this.dispatcher = dispatcher; + } + + protected override async Task Terminate(IUnsubscribeContext context) + { + var eventType = context.EventType; + + var publisherAddresses = subscriptionRouter.GetAddressesForEventType(eventType) + .EnsureNonEmpty(() => $"No publisher address could be found for message type {eventType}. Ensure the configured publisher endpoint has at least one known instance."); + + var unsubscribeTasks = new List(); + foreach (var publisherAddress in publisherAddresses) + { + Logger.Debug("Unsubscribing to " + eventType.AssemblyQualifiedName + " at publisher queue " + publisherAddress); + + var unsubscribeMessage = ControlMessageFactory.Create(MessageIntentEnum.Unsubscribe); + + unsubscribeMessage.Headers[Headers.SubscriptionMessageType] = eventType.AssemblyQualifiedName; + unsubscribeMessage.Headers[Headers.ReplyToAddress] = replyToAddress; + unsubscribeMessage.Headers[Headers.SubscriberTransportAddress] = replyToAddress; + unsubscribeMessage.Headers[Headers.SubscriberEndpoint] = endpoint; + unsubscribeMessage.Headers[Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); + unsubscribeMessage.Headers[Headers.NServiceBusVersion] = GitFlowVersion.MajorMinorPatch; + + unsubscribeTasks.Add(SendUnsubscribeMessageWithRetries(publisherAddress, unsubscribeMessage, eventType.AssemblyQualifiedName, context.Extensions)); + } + await Task.WhenAll(unsubscribeTasks.ToArray()).ConfigureAwait(false); + } + + async Task SendUnsubscribeMessageWithRetries(string destination, OutgoingMessage unsubscribeMessage, string messageType, ContextBag context, int retriesCount = 0) + { + var state = context.GetOrCreate(); + try + { + var transportOperation = new TransportOperation(unsubscribeMessage, new UnicastAddressTag(destination)); + var transportTransaction = context.GetOrCreate(); + await dispatcher.Dispatch(new TransportOperations(transportOperation), transportTransaction, context).ConfigureAwait(false); + } + catch (QueueNotFoundException ex) + { + if (retriesCount < state.MaxRetries) + { + await Task.Delay(state.RetryDelay).ConfigureAwait(false); + await SendUnsubscribeMessageWithRetries(destination, unsubscribeMessage, messageType, context, ++retriesCount).ConfigureAwait(false); + } + else + { + string message = $"Failed to unsubscribe for {messageType} at publisher queue {destination}, reason {ex.Message}"; + Logger.Error(message, ex); + throw new QueueNotFoundException(destination, message, ex); + } + } + } + + readonly string endpoint; + IDispatchMessages dispatcher; + string replyToAddress; + + SubscriptionRouter subscriptionRouter; + + static ILog Logger = LogManager.GetLogger(); + + public class Settings + { + public Settings() + { + MaxRetries = 10; + RetryDelay = TimeSpan.FromSeconds(2); + } + + public TimeSpan RetryDelay { get; set; } + public int MaxRetries { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageType.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageType.cs new file mode 100644 index 00000000000..8440eefb1da --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/MessageType.cs @@ -0,0 +1,146 @@ +namespace NServiceBus.Unicast.Subscriptions +{ + using System; + using System.Linq; + + /// + /// Representation of a message type that clients can be subscribed to. + /// + public class MessageType + { + /// + /// Initializes the message type from the given type. + /// + public MessageType(Type type) + { + Guard.AgainstNull(nameof(type), type); + Version = type.Assembly.GetName().Version; + TypeName = type.FullName; + } + + /// + /// Initializes the message type from the given string. + /// + public MessageType(string messageTypeString) + { + Guard.AgainstNullAndEmpty(nameof(messageTypeString), messageTypeString); + var parts = messageTypeString.Split(','); + Version = ParseVersion(messageTypeString); + TypeName = parts.First(); + } + + /// + /// Initializes the message type from the given string. + /// + public MessageType(string typeName, string versionString) + { + Guard.AgainstNullAndEmpty(nameof(typeName), typeName); + Guard.AgainstNullAndEmpty(nameof(versionString), versionString); + Version = ParseVersion(versionString); + TypeName = typeName; + } + + /// + /// Initializes the message type from the given string. + /// + public MessageType(string typeName, Version version) + { + Guard.AgainstNullAndEmpty(nameof(typeName), typeName); + Guard.AgainstNull(nameof(version), version); + Version = version; + TypeName = typeName; + } + + + /// + /// TypeName of the message. + /// + public string TypeName { get; } + + /// + /// Version of the message. + /// + public Version Version { get; } + + Version ParseVersion(string versionString) + { + const string version = "Version="; + var index = versionString.IndexOf(version); + + if (index >= 0) + { + versionString = versionString.Substring(index + version.Length) + .Split(',').First(); + } + return Version.Parse(versionString); + } + + /// + /// Overridden to append Version along with Type Name. + /// + public override string ToString() + { + return TypeName + ", Version=" + Version; + } + + /// + /// Equality, only major version is used. + /// + public bool Equals(MessageType other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + if (ReferenceEquals(this, other)) + { + return true; + } + return Equals(other.TypeName, TypeName); + } + + /// + /// Equality, only Type is same. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != typeof(MessageType)) + { + return false; + } + return Equals((MessageType)obj); + } + + /// + /// Gets Hash Code. + /// + public override int GetHashCode() + { + return TypeName.GetHashCode(); + } + + /// + /// Equality. + /// + public static bool operator ==(MessageType left, MessageType right) + { + return Equals(left, right); + } + + /// + /// Equality. + /// + public static bool operator !=(MessageType left, MessageType right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/NamespacePublisherSource.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/NamespacePublisherSource.cs new file mode 100644 index 00000000000..e672fcb1e13 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/NamespacePublisherSource.cs @@ -0,0 +1,54 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Routing.MessageDrivenSubscriptions; + + class NamespacePublisherSource : IPublisherSource + { + Assembly messageAssembly; + string messageNamespace; + PublisherAddress address; + + public NamespacePublisherSource(Assembly messageAssembly, string messageNamespace, PublisherAddress address) + { + this.messageAssembly = messageAssembly; + this.address = address; + this.messageNamespace = messageNamespace; + } + + public IEnumerable GenerateWithBestPracticeEnforcement(Conventions conventions) + { + var entries = messageAssembly.GetTypes() + .Where(t => conventions.IsEventType(t) && string.Equals(t.Namespace, messageNamespace, StringComparison.OrdinalIgnoreCase)) + .Select(t => new PublisherTableEntry(t, address)) + .ToArray(); + + if (!entries.Any()) + { + throw new Exception($"Cannot configure publisher for namespace {messageNamespace} because it contains no types considered as events. Event types have to either implement NServiceBus.IEvent interface or match a defined event convention."); + } + + return entries; + } + + public IEnumerable GenerateWithoutBestPracticeEnforcement(Conventions conventions) + { + var entries = messageAssembly.GetTypes() + .Where(t => conventions.IsMessageType(t) && !conventions.IsCommandType(t) && string.Equals(t.Namespace, messageNamespace, StringComparison.OrdinalIgnoreCase)) + .Select(t => new PublisherTableEntry(t, address)) + .ToArray(); + + if (!entries.Any()) + { + throw new Exception($"Cannot configure publisher for namespace {messageNamespace} because it contains no types considered as messages. Message types have to either implement NServiceBus.IMessage interface or match a defined convention."); + } + + return entries; + } + + public RouteSourcePriority Priority => RouteSourcePriority.Namespace; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherAddress.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherAddress.cs new file mode 100644 index 00000000000..a08b4c0d285 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherAddress.cs @@ -0,0 +1,133 @@ +namespace NServiceBus.Routing.MessageDrivenSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents an address of a publisher. + /// + public class PublisherAddress + { + /// + /// Creates a new publisher based on the endpoint name. + /// + public static PublisherAddress CreateFromEndpointName(string endpoint) + { + Guard.AgainstNull(nameof(endpoint), endpoint); + return new PublisherAddress { endpoint = endpoint }; + } + + /// + /// Creates a new publisher based on a set of endpoint instance names. + /// + public static PublisherAddress CreateFromEndpointInstances(params EndpointInstance[] instances) + { + Guard.AgainstNull(nameof(instances), instances); + if (instances.Length == 0) + { + throw new ArgumentException("You have to provide at least one instance."); + } + return new PublisherAddress { instances = instances }; + } + + /// + /// Creates a new publisher based on a set of physical addresses. + /// + public static PublisherAddress CreateFromPhysicalAddresses(params string[] addresses) + { + Guard.AgainstNull(nameof(addresses), addresses); + if (addresses.Length == 0) + { + throw new ArgumentException("You need to provide at least one address."); + } + return new PublisherAddress { addresses = addresses }; + } + + private PublisherAddress() + { + } + + internal IEnumerable Resolve(Func> instanceResolver, Func addressResolver) + { + if (addresses != null) + { + return addresses; + } + if (instances != null) + { + return instances.Select(addressResolver); + } + var result = instanceResolver(endpoint); + return result.Select(addressResolver); + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() + { + if (endpoint != null) + { + return endpoint; + } + if (instances != null) + { + return string.Join(", ", instances.Select(x => $"[{x.ToString()}]").OrderBy(x => x)); + } + return string.Join(", ", addresses.Select(x => $"<{x}>").OrderBy(x => x)); + } + + bool Equals(PublisherAddress other) + { + return CollectionEquals(addresses, other.addresses) + && string.Equals(endpoint, other.endpoint) + && CollectionEquals(instances, other.instances); + } + + bool CollectionEquals(IEnumerable left, IEnumerable right) + { + if (ReferenceEquals(null, left) && ReferenceEquals(null, right)) + { + return true; + } + if (ReferenceEquals(null, left) || ReferenceEquals(null, right)) + { + return false; + } + return left.SequenceEqual(right); + } + + /// Determines whether the specified object is equal to the current object. + /// true if the specified object is equal to the current object; otherwise, false. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PublisherAddress) obj); + } + + /// Serves as the default hash function. + /// A hash code for the current object. + public override int GetHashCode() + { + unchecked + { + var hashCode = addresses != null ? CollectionHashCode(addresses) : 0; + hashCode = (hashCode*397) ^ (endpoint != null ? endpoint.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (instances != null ? CollectionHashCode(instances) : 0); + return hashCode; + } + } + + static int CollectionHashCode(IEnumerable collection) + { + return collection.Aggregate(0, (acc, v) => (acc*397) ^ v.GetHashCode()); + } + + string[] addresses; + string endpoint; + EndpointInstance[] instances; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherTableEntry.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherTableEntry.cs new file mode 100644 index 00000000000..b82956fe6f0 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/PublisherTableEntry.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Routing.MessageDrivenSubscriptions +{ + using System; + + /// + /// Represents an entry in a publisher table. + /// + public class PublisherTableEntry + { + /// + /// Creates a new entry. + /// + public PublisherTableEntry(Type eventType, PublisherAddress address) + { + EventType = eventType; + Address = address; + } + + /// + /// Type of event. + /// + public Type EventType { get; } + + /// + /// Addres. + /// + public PublisherAddress Address { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Publishers.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Publishers.cs new file mode 100644 index 00000000000..70555be9297 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Publishers.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.Routing.MessageDrivenSubscriptions +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Manages the information about publishers. + /// + public class Publishers + { + internal IEnumerable GetPublisherFor(Type eventType) + { + HashSet addresses; + return publishers.TryGetValue(eventType, out addresses) + ? addresses + : Enumerable.Empty(); + } + + /// + /// Adds or replaces a set of publisher registrations. The registration set is identified . + /// If the method is called the first time with a given , the registrations are added. + /// If the method is called with the same multiple times, the publishers registered previously under this key are replaced. + /// + /// Key for this registration source. + /// Entries. + public void AddOrReplacePublishers(string sourceKey, IList entries) + { + lock (updateLock) + { + publisherRegistrations[sourceKey] = entries; + var newRouteTable = new Dictionary>(); + foreach (var entry in publisherRegistrations.Values.SelectMany(g => g)) + { + HashSet publishersOfThisEvent; + if (!newRouteTable.TryGetValue(entry.EventType, out publishersOfThisEvent)) + { + publishersOfThisEvent = new HashSet(); + newRouteTable[entry.EventType] = publishersOfThisEvent; + } + publishersOfThisEvent.Add(entry.Address); + } + publishers = newRouteTable; + } + } + + Dictionary> publishers = new Dictionary>(); + Dictionary> publisherRegistrations = new Dictionary>(); + object updateLock = new object(); + } +} diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/StorageInitializer.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/StorageInitializer.cs new file mode 100644 index 00000000000..dc7bb45e860 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/StorageInitializer.cs @@ -0,0 +1,37 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Features; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + class StorageInitializer : Feature + { + public StorageInitializer() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + + context.RegisterStartupTask(b => b.Build()); + } + + class CallInit : FeatureStartupTask + { + public IInitializableSubscriptionStorage SubscriptionStorage { get; set; } + + protected override Task OnStart(IMessageSession session) + { + SubscriptionStorage?.Init(); + return TaskEx.CompletedTask; + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Subscriber.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Subscriber.cs new file mode 100644 index 00000000000..fb6df3fe581 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/Subscriber.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions +{ + /// + /// Represents a subscriber in message-driven subscriptions. + /// + public class Subscriber + { + /// + /// Creates a new subscriber. + /// + /// Transport address. + /// Endpoint name (optional). + public Subscriber(string transportAddress, string endpoint) + { + TransportAddress = transportAddress; + Endpoint = endpoint; + } + + /// + /// The transport address of the subscriber. + /// + public string TransportAddress { get; } + + /// + /// The endpoint name of the subscriber or null if unknown. + /// + public string Endpoint { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs new file mode 100644 index 00000000000..3552d9d10e2 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs @@ -0,0 +1,112 @@ +namespace NServiceBus +{ + using System; + using System.Diagnostics; + using System.Threading.Tasks; + using Logging; + using Pipeline; + using Transport; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + class SubscriptionReceiverBehavior : IBehavior + { + public SubscriptionReceiverBehavior(ISubscriptionStorage subscriptionStorage, Func authorizer) + { + this.subscriptionStorage = subscriptionStorage; + this.authorizer = authorizer; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + var incomingMessage = context.Message; + var messageTypeString = GetSubscriptionMessageTypeFrom(incomingMessage); + + var intent = incomingMessage.GetMesssageIntent(); + + if (string.IsNullOrEmpty(messageTypeString) && intent != MessageIntentEnum.Subscribe && intent != MessageIntentEnum.Unsubscribe) + { + await next(context).ConfigureAwait(false); + return; + } + + if (string.IsNullOrEmpty(messageTypeString)) + { + throw new InvalidOperationException("Message intent is Subscribe, but the subscription message type header is missing."); + } + + if (intent != MessageIntentEnum.Subscribe && intent != MessageIntentEnum.Unsubscribe) + { + throw new InvalidOperationException("Subscription messages need to have intent set to Subscribe/Unsubscribe."); + } + + string subscriberAddress; + string subscriberEndpoint = null; + + if (incomingMessage.Headers.TryGetValue(Headers.SubscriberTransportAddress, out subscriberAddress)) + { + subscriberEndpoint = incomingMessage.Headers[Headers.SubscriberEndpoint]; + } + else + { + subscriberAddress = incomingMessage.GetReplyToAddress(); + } + + if (subscriberAddress == null) + { + throw new InvalidOperationException("Subscription message arrived without a valid ReplyToAddress."); + } + + if (subscriptionStorage == null) + { + var warning = $"Subscription message from {subscriberAddress} arrived at this endpoint, yet this endpoint is not configured to be a publisher. To avoid this warning make this endpoint a publisher by configuring a subscription storage or using the AsA_Publisher role."; + Logger.WarnFormat(warning); + + if (Debugger.IsAttached) // only under debug, so that we don't expose ourselves to a denial of service + { + throw new InvalidOperationException(warning); // and cause message to go to error queue by throwing an exception + } + + return; + } + + if (!authorizer(context)) + { + Logger.Debug($"{intent} from {subscriberAddress} on message type {messageTypeString} was refused."); + return; + } + Logger.Info($"{intent} from {subscriberAddress} on message type {messageTypeString}"); + var subscriber = new Subscriber(subscriberAddress, subscriberEndpoint); + if (incomingMessage.GetMesssageIntent() == MessageIntentEnum.Subscribe) + { + var messageType = new MessageType(messageTypeString); + await subscriptionStorage.Subscribe(subscriber, messageType, context.Extensions).ConfigureAwait(false); + return; + } + + await subscriptionStorage.Unsubscribe(subscriber, new MessageType(messageTypeString), context.Extensions).ConfigureAwait(false); + } + + static string GetSubscriptionMessageTypeFrom(IncomingMessage msg) + { + string value; + msg.Headers.TryGetValue(Headers.SubscriptionMessageType, out value); + return value; + } + + Func authorizer; + + ISubscriptionStorage subscriptionStorage; + + static ILog Logger = LogManager.GetLogger(); + + public class Registration : RegisterStep + { + public Registration() + : base("ProcessSubscriptionRequests", typeof(SubscriptionReceiverBehavior), "Check for subscription messages and execute the requested behavior to subscribe or unsubscribe.") + { + InsertAfterIfExists("ExecuteUnitOfWork"); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionRouter.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionRouter.cs new file mode 100644 index 00000000000..bb3cc3b24d3 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/SubscriptionRouter.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Routing; + using Routing.MessageDrivenSubscriptions; + + class SubscriptionRouter + { + public SubscriptionRouter(Publishers publishers, EndpointInstances endpointInstances, Func transportAddressTranslation) + { + this.publishers = publishers; + this.endpointInstances = endpointInstances; + this.transportAddressTranslation = transportAddressTranslation; + } + + public IEnumerable GetAddressesForEventType(Type messageType) + { + var publishersOfThisEvent = publishers.GetPublisherFor(messageType); + var publisherTransportAddresses = publishersOfThisEvent.SelectMany(p => p.Resolve(e => endpointInstances.FindInstances(e), i => transportAddressTranslation(i))); + return publisherTransportAddresses; + } + + EndpointInstances endpointInstances; + Func transportAddressTranslation; + + Publishers publishers; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/TypePublisherSource.cs b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/TypePublisherSource.cs new file mode 100644 index 00000000000..28a4b7a9dd3 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageDrivenSubscriptions/TypePublisherSource.cs @@ -0,0 +1,46 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Routing.MessageDrivenSubscriptions; + + class TypePublisherSource : IPublisherSource + { + Type messageType; + PublisherAddress address; + + public TypePublisherSource(Type messageType, PublisherAddress address) + { + this.messageType = messageType; + this.address = address; + } + + public IEnumerable GenerateWithBestPracticeEnforcement(Conventions conventions) + { + if (!conventions.IsMessageType(messageType)) + { + throw new Exception($"Cannot configure publisher for type '{messageType.FullName}' because it is not considered a message. Message types have to either implement NServiceBus.IMessage interface or match a defined message convention."); + } + if (!conventions.IsEventType(messageType)) + { + throw new Exception($"Cannot configure publisher for type '{messageType.FullName}' because it is not considered an event. Event types have to either implement NServiceBus.IEvent interface or match a defined event convention."); + } + yield return new PublisherTableEntry(messageType, address); + } + + public IEnumerable GenerateWithoutBestPracticeEnforcement(Conventions conventions) + { + if (!conventions.IsMessageType(messageType)) + { + throw new Exception($"Cannot configure publisher for type '{messageType.FullName}' because it is not considered a message. Message types have to either implement NServiceBus.IMessage interface or match a defined message convention."); + } + if (conventions.IsCommandType(messageType)) + { + throw new Exception($"Cannot configure publisher for type '{messageType.FullName}' because it is a command."); + } + yield return new PublisherTableEntry(messageType, address); + } + + public RouteSourcePriority Priority => RouteSourcePriority.Type; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageEndpointMapping.cs b/src/NServiceBus.Core/Routing/MessageEndpointMapping.cs new file mode 100644 index 00000000000..aecd841baa2 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageEndpointMapping.cs @@ -0,0 +1,229 @@ +namespace NServiceBus.Config +{ + using System; + using System.Configuration; + using System.IO; + using System.Linq; + using System.Reflection; + + /// + /// A configuration element representing which message types map to which endpoint. + /// + public class MessageEndpointMapping : ConfigurationElement, IComparable + { + /// + /// A string defining the message assembly, or single message type. + /// + [ConfigurationProperty("Messages", IsRequired = false)] + public string Messages + { + get { return (string) this["Messages"]; } + set { this["Messages"] = value; } + } + + /// + /// The endpoint named according to "queue@machine". + /// + [ConfigurationProperty("Endpoint", IsRequired = true)] + public string Endpoint + { + get { return (string) this["Endpoint"]; } + set { this["Endpoint"] = value; } + } + + /// + /// The message assembly for the endpoint mapping. + /// + [ConfigurationProperty("Assembly", IsRequired = false)] + public string AssemblyName + { + get { return (string) this["Assembly"]; } + set { this["Assembly"] = value; } + } + + /// + /// The fully qualified name of the message type. Define this if you want to map a single message type to the endpoint. + /// + /// Type will take preference above namespace. + [ConfigurationProperty("Type", IsRequired = false)] + public string TypeFullName + { + get { return (string) this["Type"]; } + set { this["Type"] = value; } + } + + /// + /// The message namespace. Define this if you want to map all the types in the namespace to the endpoint. + /// + /// Sub-namespaces will not be mapped. + [ConfigurationProperty("Namespace", IsRequired = false)] + public string Namespace + { + get { return (string) this["Namespace"]; } + set { this["Namespace"] = value; } + } + + /// + /// Comparison support. + /// + public int CompareTo(MessageEndpointMapping other) + { + if (!string.IsNullOrWhiteSpace(TypeFullName) || HaveMessagesMappingWithType(this)) + { + if (!string.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) + { + return 0; + } + + return -1; + } + + if (!string.IsNullOrWhiteSpace(Namespace)) + { + if (!string.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) + { + return 1; + } + + if (!string.IsNullOrWhiteSpace(other.Namespace)) + { + return 0; + } + + return -1; + } + + if (!string.IsNullOrWhiteSpace(other.TypeFullName) || HaveMessagesMappingWithType(other)) + { + return 1; + } + + if (!string.IsNullOrWhiteSpace(other.Namespace)) + { + return 1; + } + + if (!string.IsNullOrWhiteSpace(other.AssemblyName) || !string.IsNullOrWhiteSpace(other.Messages)) + { + return 0; + } + + return -1; + } + + /// + /// Uses the configuration properties to configure the endpoint mapping. + /// + public void Configure(Action mapTypeToEndpoint) + { + Guard.AgainstNull(nameof(mapTypeToEndpoint), mapTypeToEndpoint); + if (!string.IsNullOrWhiteSpace(Messages)) + { + ConfigureEndpointMappingUsingMessagesProperty(mapTypeToEndpoint); + return; + } + + var address = Endpoint; + var assemblyName = AssemblyName; + var ns = Namespace; + var typeFullName = TypeFullName; + + if (string.IsNullOrWhiteSpace(assemblyName)) + { + throw new ArgumentException("Could not process message endpoint mapping. The Assembly property is not defined. Either the Assembly or Messages property is required."); + } + + var a = GetMessageAssembly(assemblyName); + + if (!string.IsNullOrWhiteSpace(typeFullName)) + { + try + { + var t = a.GetType(typeFullName, false); + + if (t == null) + { + throw new ArgumentException($"Could not process message endpoint mapping. Cannot find the type '{typeFullName}' in the assembly '{assemblyName}'. Ensure that you are using the full name for the type."); + } + + mapTypeToEndpoint(t, address); + + return; + } + catch (BadImageFormatException ex) + { + throw new ArgumentException($"Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type '{typeFullName}' in the assembly '{assemblyName}'", ex); + } + catch (FileLoadException ex) + { + throw new ArgumentException($"Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type '{typeFullName}' in the assembly '{assemblyName}'", ex); + } + } + + var messageTypes = a.GetTypes().AsQueryable(); + + if (!string.IsNullOrEmpty(ns)) + { + messageTypes = messageTypes.Where(t => !string.IsNullOrWhiteSpace(t.Namespace) && t.Namespace.Equals(ns, StringComparison.InvariantCultureIgnoreCase)); + } + + foreach (var t in messageTypes) + { + mapTypeToEndpoint(t, address); + } + } + + void ConfigureEndpointMappingUsingMessagesProperty(Action mapTypeToEndpoint) + { + var address = Endpoint; + var messages = Messages; + + try + { + var messageType = Type.GetType(messages, false); + if (messageType != null) + { + mapTypeToEndpoint(messageType, address); + return; + } + } + catch (BadImageFormatException ex) + { + throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type: " + messages), ex); + } + catch (FileLoadException ex) + { + throw new ArgumentException(string.Format("Could not process message endpoint mapping. Could not load the assembly or one of its dependencies for type: " + messages), ex); + } + + var messagesAssembly = GetMessageAssembly(messages); + + foreach (var t in messagesAssembly.GetTypes()) + { + mapTypeToEndpoint(t, address); + } + } + + static Assembly GetMessageAssembly(string assemblyName) + { + try + { + return Assembly.Load(assemblyName); + } + catch (Exception ex) + { + throw new ArgumentException("Could not process message endpoint mapping. Problem loading message assembly: " + assemblyName, ex); + } + } + + static bool HaveMessagesMappingWithType(MessageEndpointMapping mapping) + { + if (string.IsNullOrWhiteSpace(mapping.Messages)) + { + return false; + } + + return Type.GetType(mapping.Messages, false) != null; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessageEndpointMappingCollection.cs b/src/NServiceBus.Core/Routing/MessageEndpointMappingCollection.cs new file mode 100644 index 00000000000..7b20e1e177f --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessageEndpointMappingCollection.cs @@ -0,0 +1,239 @@ +namespace NServiceBus.Config +{ + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Linq; + using Routing; + using Routing.MessageDrivenSubscriptions; + + /// + /// A configuration element collection of MessageEndpointMappings. + /// + public class MessageEndpointMappingCollection : ConfigurationElementCollection + { + /// + /// Returns AddRemoveClearMap. + /// + public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.AddRemoveClearMap; + + /// + /// Calls the base AddElementName. + /// + public new string AddElementName + { + get { return base.AddElementName; } + + set { base.AddElementName = value; } + } + + /// + /// Calls the base ClearElementName. + /// + public new string ClearElementName + { + get { return base.ClearElementName; } + + set { base.AddElementName = value; } + } + + /// + /// Returns the base RemoveElementName. + /// + public new string RemoveElementName => base.RemoveElementName; + + /// + /// Returns the base Count. + /// + public new int Count => base.Count; + + /// + /// Gets/sets the MessageEndpointMapping at the given index. + /// + public MessageEndpointMapping this[int index] + { + get { return (MessageEndpointMapping)BaseGet(index); } + set + { + if (BaseGet(index) != null) + { + BaseRemoveAt(index); + } + BaseAdd(index, value); + } + } + + /// + /// Gets the MessageEndpointMapping for the given name. + /// + new public MessageEndpointMapping this[string Name] => (MessageEndpointMapping)BaseGet(Name); + + /// + /// Creates a new MessageEndpointMapping. + /// + protected override ConfigurationElement CreateNewElement() + { + return new MessageEndpointMapping(); + } + + /// + /// Creates a new MessageEndpointMapping, setting its Message property to the given name. + /// + protected override ConfigurationElement CreateNewElement(string elementName) + { + var result = new MessageEndpointMapping + { + Messages = elementName + }; + + return result; + } + + /// + /// Returns the Messages property of the given MessageEndpointMapping element. + /// + protected override object GetElementKey(ConfigurationElement element) + { + var messageEndpointMapping = (MessageEndpointMapping)element; + + return $"{messageEndpointMapping.Messages}{messageEndpointMapping.AssemblyName}{messageEndpointMapping.TypeFullName}{messageEndpointMapping.Namespace}"; + } + + /// + /// Calls BaseIndexOf on the given mapping. + /// + public int IndexOf(MessageEndpointMapping mapping) + { + return BaseIndexOf(mapping); + } + + /// + /// Calls BaseAdd. + /// + public void Add(MessageEndpointMapping mapping) + { + BaseAdd(mapping); + } + + /// + /// Calls BaseAdd with true as the additional parameter. + /// + protected override void BaseAdd(ConfigurationElement element) + { + BaseAdd(element, true); + } + + /// + /// If the mapping exists, calls BaseRemove on it. + /// + public void Remove(MessageEndpointMapping mapping) + { + if (BaseIndexOf(mapping) >= 0) + { + BaseRemove(mapping.Messages); + } + } + + /// + /// Calls BaseRemoveAt. + /// + public void RemoveAt(int index) + { + BaseRemoveAt(index); + } + + /// + /// Calls BaseRemove. + /// + public void Remove(string name) + { + BaseRemove(name); + } + + /// + /// Calls BaseClear. + /// + public void Clear() + { + BaseClear(); + } + + /// + /// True if the collection is readonly. + /// + public override bool IsReadOnly() + { + return false; + } + + internal void Apply(Publishers publishers, UnicastRoutingTable unicastRoutingTable, Func makeCanonicalAddress, Conventions conventions) + { + var routeTableEntries = new Dictionary(); + var publisherTableEntries = new List(); + + foreach (var m in this.Cast().OrderByDescending(m => m)) + { + m.Configure((type, endpointAddress) => + { + if (!conventions.IsMessageType(type)) + { + return; + } + var canonicalForm = makeCanonicalAddress(endpointAddress); + var baseTypes = GetBaseTypes(type, conventions); + + RegisterMessageRoute(type, canonicalForm, routeTableEntries, baseTypes); + RegisterEventRoute(type, canonicalForm, publisherTableEntries, baseTypes); + }); + } + + publishers.AddOrReplacePublishers("MessageEndpointMappings", publisherTableEntries); + unicastRoutingTable.AddOrReplaceRoutes("MessageEndpointMappings", routeTableEntries.Values.ToList()); + } + + static void RegisterEventRoute(Type mappedType, string address, List publisherTableEntries, IEnumerable baseTypes) + { + var publisherAddress = PublisherAddress.CreateFromPhysicalAddresses(address); + publisherTableEntries.AddRange(baseTypes.Select(type => new PublisherTableEntry(type, publisherAddress))); + publisherTableEntries.Add(new PublisherTableEntry(mappedType, publisherAddress)); + } + + static void RegisterMessageRoute(Type mappedType, string address, Dictionary routeTableEntries, IEnumerable baseTypes) + { + var route = UnicastRoute.CreateFromPhysicalAddress(address); + foreach (var baseType in baseTypes) + { + routeTableEntries[baseType] = new RouteTableEntry(baseType, route); + } + routeTableEntries[mappedType] = new RouteTableEntry(mappedType, route); + } + + static List GetBaseTypes(Type messageType, Conventions conventions) + { + var result = new List(); + var baseType = messageType.BaseType; + while (baseType != typeof(object) && baseType != null) + { + if (conventions.IsMessageType(baseType)) + { + if (!result.Contains(baseType)) + { + result.Add(baseType); + } + } + + baseType = baseType.BaseType; + } + + foreach (var i in messageType.GetInterfaces()) + { + if (conventions.IsMessageType(i) && !result.Contains(i)) + { + result.Add(i); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/BestPracticesOptionExtensions.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/BestPracticesOptionExtensions.cs new file mode 100644 index 00000000000..ad84754c719 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/BestPracticesOptionExtensions.cs @@ -0,0 +1,80 @@ +namespace NServiceBus +{ + using Extensibility; + using Pipeline; + + /// + /// Provides options for disabling the best practice enforcement. + /// + public static class BestPracticesOptionExtensions + { + /// + /// Turns off the best practice enforcement for the given message. + /// + public static void DoNotEnforceBestPractices(this ExtendableOptions options) + { + options.Context.SetDoNotEnforceBestPractices(); + } + + /// + /// Returns whether has ben called or + /// not. + /// + /// true if best practice enforcement has ben disabled, false otherwise. + public static bool IgnoredBestPractices(this ExtendableOptions options) + { + EnforceBestPracticesOptions bestPracticesOptions; + options.Context.TryGet(out bestPracticesOptions); + return !(bestPracticesOptions?.Enabled ?? true); + } + + /// + /// Turns off the best practice enforcement for the given context. + /// + public static void DoNotEnforceBestPractices(this IOutgoingReplyContext context) + { + context.Extensions.SetDoNotEnforceBestPractices(); + } + + /// + /// Turns off the best practice enforcement for the given context. + /// + public static void DoNotEnforceBestPractices(this IOutgoingSendContext context) + { + context.Extensions.SetDoNotEnforceBestPractices(); + } + + /// + /// Turns off the best practice enforcement for the given context. + /// + public static void DoNotEnforceBestPractices(this ISubscribeContext context) + { + context.Extensions.SetDoNotEnforceBestPractices(); + } + + /// + /// Turns off the best practice enforcement for the given context. + /// + public static void DoNotEnforceBestPractices(this IOutgoingPublishContext context) + { + context.Extensions.SetDoNotEnforceBestPractices(); + } + + /// + /// Turns off the best practice enforcement for the given context. + /// + public static void DoNotEnforceBestPractices(this IUnsubscribeContext context) + { + context.Extensions.SetDoNotEnforceBestPractices(); + } + + static void SetDoNotEnforceBestPractices(this ContextBag context) + { + var bestPracticesOptions = new EnforceBestPracticesOptions + { + Enabled = false + }; + context.Set(bestPracticesOptions); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceBestPracticesOptions.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceBestPracticesOptions.cs new file mode 100644 index 00000000000..97a9b6f8a4b --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceBestPracticesOptions.cs @@ -0,0 +1,12 @@ +namespace NServiceBus +{ + class EnforceBestPracticesOptions + { + public EnforceBestPracticesOptions() + { + Enabled = true; + } + + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforcePublishBestPracticesBehavior.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforcePublishBestPracticesBehavior.cs new file mode 100644 index 00000000000..1e481b31c22 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforcePublishBestPracticesBehavior.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class EnforcePublishBestPracticesBehavior : IBehavior + { + public EnforcePublishBestPracticesBehavior(Validations validations) + { + this.validations = validations; + } + + public Task Invoke(IOutgoingPublishContext context, Func next) + { + EnforceBestPracticesOptions options; + + if (!context.Extensions.TryGet(out options) || options.Enabled) + { + validations.AssertIsValidForPubSub(context.Message.MessageType); + } + + return next(context); + } + + Validations validations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceReplyBestPracticesBehavior.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceReplyBestPracticesBehavior.cs new file mode 100644 index 00000000000..16bb9902e9f --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceReplyBestPracticesBehavior.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class EnforceReplyBestPracticesBehavior : IBehavior + { + public EnforceReplyBestPracticesBehavior(Validations validations) + { + this.validations = validations; + } + + public Task Invoke(IOutgoingReplyContext context, Func next) + { + EnforceBestPracticesOptions options; + + if (!context.Extensions.TryGet(out options) || options.Enabled) + { + validations.AssertIsValidForReply(context.Message.MessageType); + } + + return next(context); + } + + Validations validations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSendBestPracticesBehavior.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSendBestPracticesBehavior.cs new file mode 100644 index 00000000000..093f69d2a58 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSendBestPracticesBehavior.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class EnforceSendBestPracticesBehavior : IBehavior + { + public EnforceSendBestPracticesBehavior(Validations validations) + { + this.validations = validations; + } + + public Task Invoke(IOutgoingSendContext context, Func next) + { + EnforceBestPracticesOptions options; + + if (!context.Extensions.TryGet(out options) || options.Enabled) + { + validations.AssertIsValidForSend(context.Message.MessageType); + } + + return next(context); + } + + Validations validations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSubscribeBestPracticesBehavior.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSubscribeBestPracticesBehavior.cs new file mode 100644 index 00000000000..1d09710614f --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceSubscribeBestPracticesBehavior.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class EnforceSubscribeBestPracticesBehavior : IBehavior + { + public EnforceSubscribeBestPracticesBehavior(Validations validations) + { + this.validations = validations; + } + + public Task Invoke(ISubscribeContext context, Func next) + { + EnforceBestPracticesOptions options; + + if (!context.Extensions.TryGet(out options) || options.Enabled) + { + validations.AssertIsValidForPubSub(context.EventType); + } + + return next(context); + } + + Validations validations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceUnsubscribeBestPracticesBehavior.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceUnsubscribeBestPracticesBehavior.cs new file mode 100644 index 00000000000..7d61c0e22b1 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/EnforceUnsubscribeBestPracticesBehavior.cs @@ -0,0 +1,28 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class EnforceUnsubscribeBestPracticesBehavior : IBehavior + { + public EnforceUnsubscribeBestPracticesBehavior(Validations validations) + { + this.validations = validations; + } + + public Task Invoke(IUnsubscribeContext context, Func next) + { + EnforceBestPracticesOptions options; + + if (!context.Extensions.TryGet(out options) || options.Enabled) + { + validations.AssertIsValidForPubSub(context.EventType); + } + + return next(context); + } + + Validations validations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MessagingBestPractices/Validations.cs b/src/NServiceBus.Core/Routing/MessagingBestPractices/Validations.cs new file mode 100644 index 00000000000..a8c74dfd560 --- /dev/null +++ b/src/NServiceBus.Core/Routing/MessagingBestPractices/Validations.cs @@ -0,0 +1,57 @@ +namespace NServiceBus +{ + using System; + using Logging; + + class Validations + { + public Validations(Conventions conventions) + { + this.conventions = conventions; + } + + public void AssertIsValidForSend(Type messageType) + { + if (conventions.IsInSystemConventionList(messageType)) + { + return; + } + if (!conventions.IsEventType(messageType)) + { + return; + } + throw new Exception("Events can have multiple recipient so they should be published."); + } + + public void AssertIsValidForReply(Type messageType) + { + if (conventions.IsInSystemConventionList(messageType)) + { + return; + } + if (!conventions.IsCommandType(messageType) && !conventions.IsEventType(messageType)) + { + return; + } + throw new Exception("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner. Events should be published."); + } + + public void AssertIsValidForPubSub(Type messageType) + { + if (conventions.IsCommandType(messageType)) + { + throw new Exception("Pub/Sub is not supported for Commands. They should be be sent direct to their logical owner."); + } + + if (!conventions.IsEventType(messageType)) + { + Log.Info("You are using a basic message to do pub/sub, consider implementing the more specific ICommand and IEvent interfaces to help NServiceBus to enforce messaging best practices for you."); + } + } + + Conventions conventions; + + + static ILog Log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MulticastAddressTag.cs b/src/NServiceBus.Core/Routing/MulticastAddressTag.cs new file mode 100644 index 00000000000..2cb6c3e8f8b --- /dev/null +++ b/src/NServiceBus.Core/Routing/MulticastAddressTag.cs @@ -0,0 +1,24 @@ +namespace NServiceBus.Routing +{ + using System; + + /// + /// Represents a route that should deliver the message to all interested subscribers. + /// + public class MulticastAddressTag : AddressTag + { + /// + /// Initializes a new instance of . + /// + /// The event being published. + public MulticastAddressTag(Type messageType) + { + MessageType = messageType; + } + + /// + /// The event being published. + /// + public Type MessageType { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/MulticastRoutingStrategy.cs b/src/NServiceBus.Core/Routing/MulticastRoutingStrategy.cs new file mode 100644 index 00000000000..d7354d772ab --- /dev/null +++ b/src/NServiceBus.Core/Routing/MulticastRoutingStrategy.cs @@ -0,0 +1,30 @@ +namespace NServiceBus.Routing +{ + using System; + using System.Collections.Generic; + + /// + /// A routing strategy for multicast routing. + /// + public class MulticastRoutingStrategy : RoutingStrategy + { + /// + /// Creates new routing strategy. + /// + public MulticastRoutingStrategy(Type messageType) + { + this.messageType = messageType; + } + + /// + /// Applies the routing strategy to the message. + /// + /// Message headers. + public override AddressTag Apply(Dictionary headers) + { + return new MulticastAddressTag(messageType); + } + + Type messageType; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/NamespaceRouteSource.cs b/src/NServiceBus.Core/Routing/NamespaceRouteSource.cs new file mode 100644 index 00000000000..e8404a6fd88 --- /dev/null +++ b/src/NServiceBus.Core/Routing/NamespaceRouteSource.cs @@ -0,0 +1,37 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Routing; + + class NamespaceRouteSource : IRouteSource + { + Assembly messageAssembly; + string messageNamespace; + UnicastRoute route; + + public NamespaceRouteSource(Assembly messageAssembly, string messageNamespace, UnicastRoute route) + { + this.messageAssembly = messageAssembly; + this.route = route; + this.messageNamespace = messageNamespace; + } + + public IEnumerable GenerateRoutes(Conventions conventions) + { + var routes = messageAssembly.GetTypes() + .Where(t => conventions.IsMessageType(t) && string.Equals(t.Namespace, messageNamespace, StringComparison.OrdinalIgnoreCase)) + .Select(t => new RouteTableEntry(t, route)) + .ToArray(); + if (!routes.Any()) + { + throw new Exception($"Cannot configure routing for namespace {messageNamespace} because it contains no types considered as messages. Message types have to either implement NServiceBus.IMessage interface or match a defined message convention."); + } + return routes; + } + + public RouteSourcePriority Priority => RouteSourcePriority.Namespace; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/NativeSubscribeTerminator.cs b/src/NServiceBus.Core/Routing/NativeSubscribeTerminator.cs new file mode 100644 index 00000000000..8572f6789af --- /dev/null +++ b/src/NServiceBus.Core/Routing/NativeSubscribeTerminator.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class NativeSubscribeTerminator : PipelineTerminator + { + public NativeSubscribeTerminator(IManageSubscriptions subscriptionManager) + { + this.subscriptionManager = subscriptionManager; + } + + protected override Task Terminate(ISubscribeContext context) + { + return subscriptionManager.Subscribe(context.EventType, context.Extensions); + } + + IManageSubscriptions subscriptionManager; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/NativeUnsubscribeTerminator.cs b/src/NServiceBus.Core/Routing/NativeUnsubscribeTerminator.cs new file mode 100644 index 00000000000..c3ad676a645 --- /dev/null +++ b/src/NServiceBus.Core/Routing/NativeUnsubscribeTerminator.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Transport; + + class NativeUnsubscribeTerminator : PipelineTerminator + { + public NativeUnsubscribeTerminator(IManageSubscriptions subscriptionManager) + { + this.subscriptionManager = subscriptionManager; + } + + protected override Task Terminate(IUnsubscribeContext context) + { + return subscriptionManager.Unsubscribe(context.EventType, context.Extensions); + } + + IManageSubscriptions subscriptionManager; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/NonDurableDelivery.cs b/src/NServiceBus.Core/Routing/NonDurableDelivery.cs new file mode 100644 index 00000000000..2bdc566d8f2 --- /dev/null +++ b/src/NServiceBus.Core/Routing/NonDurableDelivery.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using DeliveryConstraints; + + /// + /// Instructs the transport that it's allowed to transport the message with out the need to store it durable. + /// + public class NonDurableDelivery : DeliveryConstraint + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RouteSourcePriority.cs b/src/NServiceBus.Core/Routing/RouteSourcePriority.cs new file mode 100644 index 00000000000..535854fed46 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RouteSourcePriority.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + enum RouteSourcePriority + { + Assembly = 0, + Namespace = 1, + Type = 2 + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RouteTableEntry.cs b/src/NServiceBus.Core/Routing/RouteTableEntry.cs new file mode 100644 index 00000000000..d8ec01d6f24 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RouteTableEntry.cs @@ -0,0 +1,28 @@ +namespace NServiceBus.Routing +{ + using System; + + /// + /// Represents an entry in a routing table. + /// + public class RouteTableEntry + { + /// + /// Type of message. + /// + public Type MessageType { get; } + /// + /// Route for the message type. + /// + public UnicastRoute Route { get; } + + /// + /// Creates a new entry. + /// + public RouteTableEntry(Type messageType, UnicastRoute route) + { + this.MessageType = messageType; + this.Route = route; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Routers/MulticastPublishRouterBehavior.cs b/src/NServiceBus.Core/Routing/Routers/MulticastPublishRouterBehavior.cs new file mode 100644 index 00000000000..15519a0eadf --- /dev/null +++ b/src/NServiceBus.Core/Routing/Routers/MulticastPublishRouterBehavior.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Routing; + + class MulticastPublishRouterBehavior : StageConnector + { + public override Task Invoke(IOutgoingPublishContext context, Func stage) + { + context.Headers[Headers.MessageIntent] = MessageIntentEnum.Publish.ToString(); + + var logicalMessageContext = this.CreateOutgoingLogicalMessageContext( + context.Message, + new[] + { + new MulticastRoutingStrategy(context.Message.MessageType) + }, + context); + + return stage(logicalMessageContext); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Routers/UnicastPublishRouterConnector.cs b/src/NServiceBus.Core/Routing/Routers/UnicastPublishRouterConnector.cs new file mode 100644 index 00000000000..909c08a2308 --- /dev/null +++ b/src/NServiceBus.Core/Routing/Routers/UnicastPublishRouterConnector.cs @@ -0,0 +1,50 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Unicast.Queuing; + + class UnicastPublishRouterConnector : StageConnector + { + public UnicastPublishRouterConnector(IUnicastPublishRouter unicastPublishRouter, DistributionPolicy distributionPolicy) + { + this.unicastPublishRouter = unicastPublishRouter; + this.distributionPolicy = distributionPolicy; + } + + public override async Task Invoke(IOutgoingPublishContext context, Func stage) + { + var eventType = context.Message.MessageType; + var addressLabels = await GetRoutingStrategies(context, eventType).ConfigureAwait(false); + if (addressLabels.Count == 0) + { + //No subscribers for this message. + return; + } + + context.Headers[Headers.MessageIntent] = MessageIntentEnum.Publish.ToString(); + + try + { + await stage(this.CreateOutgoingLogicalMessageContext(context.Message, addressLabels, context)).ConfigureAwait(false); + } + catch (QueueNotFoundException ex) + { + throw new Exception($"The destination queue '{ex.Queue}' could not be found. The destination may be misconfigured for this kind of message ({eventType}) in the MessageEndpointMappings of the UnicastBusConfig section in the configuration file. It may also be the case that the given queue hasn\'t been created yet, or has been deleted.", ex); + } + } + + async Task> GetRoutingStrategies(IOutgoingPublishContext context, Type eventType) + { + var addressLabels = await unicastPublishRouter.Route(eventType, distributionPolicy, context.Extensions).ConfigureAwait(false); + return addressLabels.ToList(); + } + + DistributionPolicy distributionPolicy; + IUnicastPublishRouter unicastPublishRouter; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Routers/UnicastReplyRouterConnector.cs b/src/NServiceBus.Core/Routing/Routers/UnicastReplyRouterConnector.cs new file mode 100644 index 00000000000..6b3ec1b01b3 --- /dev/null +++ b/src/NServiceBus.Core/Routing/Routers/UnicastReplyRouterConnector.cs @@ -0,0 +1,69 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Transport; + using Unicast.Queuing; + + class UnicastReplyRouterConnector : StageConnector + { + public override async Task Invoke(IOutgoingReplyContext context, Func stage) + { + var state = context.Extensions.GetOrCreate(); + + var replyToAddress = state.ExplicitDestination; + + if (string.IsNullOrEmpty(replyToAddress)) + { + replyToAddress = GetReplyToAddressFromIncomingMessage(context); + } + + context.Headers[Headers.MessageIntent] = MessageIntentEnum.Reply.ToString(); + + var addressLabels = RouteToDestination(replyToAddress).EnsureNonEmpty(() => "No destination specified.").ToArray(); + var logicalMessageContext = this.CreateOutgoingLogicalMessageContext(context.Message, addressLabels, context); + + try + { + await stage(logicalMessageContext).ConfigureAwait(false); + } + catch (QueueNotFoundException ex) + { + throw new Exception($"The destination queue '{ex.Queue}' could not be found. It may be the case that the given queue hasn't been created yet, or has been deleted.", ex); + } + } + + static string GetReplyToAddressFromIncomingMessage(IOutgoingReplyContext context) + { + IncomingMessage incomingMessage; + + if (!context.TryGetIncomingPhysicalMessage(out incomingMessage)) + { + throw new Exception("No incoming message found, replies are only valid to call from a message handler"); + } + + string replyToAddress; + + if (!incomingMessage.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress)) + { + throw new Exception($"No `ReplyToAddress` found on the {context.Message.MessageType.FullName} being processed"); + } + + return replyToAddress; + } + + static IEnumerable RouteToDestination(string physicalAddress) + { + yield return new UnicastRoutingStrategy(physicalAddress); + } + + public class State + { + public string ExplicitDestination { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/Routers/UnicastSendRouterConnector.cs b/src/NServiceBus.Core/Routing/Routers/UnicastSendRouterConnector.cs new file mode 100644 index 00000000000..944234b97ab --- /dev/null +++ b/src/NServiceBus.Core/Routing/Routers/UnicastSendRouterConnector.cs @@ -0,0 +1,112 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Unicast.Queuing; + + class UnicastSendRouterConnector : StageConnector + { + public enum RouteOption + { + None, + ExplicitDestination, + RouteToThisInstance, + RouteToAnyInstanceOfThisEndpoint, + RouteToSpecificInstance + } + + public UnicastSendRouterConnector( + string sharedQueue, + string instanceSpecificQueue, + IUnicastSendRouter unicastSendRouter, + DistributionPolicy distributionPolicy, + Func transportAddressTranslation) + { + this.sharedQueue = sharedQueue; + this.instanceSpecificQueue = instanceSpecificQueue; + this.unicastSendRouter = unicastSendRouter; + defaultDistributionPolicy = distributionPolicy; + this.transportAddressTranslation = transportAddressTranslation; + } + + public override async Task Invoke(IOutgoingSendContext context, Func stage) + { + var messageType = context.Message.MessageType; + + var state = context.Extensions.GetOrCreate(); + + if (state.Option == RouteOption.RouteToThisInstance && instanceSpecificQueue == null) + { + throw new InvalidOperationException("Cannot route to a specific instance because an endpoint instance discriminator was not configured for the destination endpoint. It can be specified via EndpointConfiguration.MakeInstanceUniquelyAddressable(string discriminator)."); + } + var thisEndpoint = state.Option == RouteOption.RouteToAnyInstanceOfThisEndpoint ? sharedQueue : null; + var thisInstance = state.Option == RouteOption.RouteToThisInstance ? instanceSpecificQueue : null; + var explicitDestination = state.Option == RouteOption.ExplicitDestination ? state.ExplicitDestination : null; + var destination = explicitDestination ?? thisInstance ?? thisEndpoint; + + var distributionPolicy = state.Option == RouteOption.RouteToSpecificInstance ? new SpecificInstanceDistributionPolicy(state.SpecificInstance, transportAddressTranslation) : defaultDistributionPolicy; + + var routingStrategy = string.IsNullOrEmpty(destination) + ? unicastSendRouter.Route(messageType, distributionPolicy) + : RouteToDestination(destination); + + if (routingStrategy == null) + { + throw new Exception($"No destination specified for message: {messageType}"); + } + + context.Headers[Headers.MessageIntent] = MessageIntentEnum.Send.ToString(); + + var logicalMessageContext = this.CreateOutgoingLogicalMessageContext( + context.Message, + new[] + { + routingStrategy + }, + context); + + try + { + await stage(logicalMessageContext).ConfigureAwait(false); + } + catch (QueueNotFoundException ex) + { + throw new Exception($"The destination queue '{ex.Queue}' could not be found. The destination may be misconfigured for this kind of message ({messageType}) in the MessageEndpointMappings of the UnicastBusConfig section in the configuration file. It may also be the case that the given queue hasn't been created yet, or has been deleted.", ex); + } + } + + static UnicastRoutingStrategy RouteToDestination(string physicalAddress) + { + return new UnicastRoutingStrategy(physicalAddress); + } + + IDistributionPolicy defaultDistributionPolicy; + Func transportAddressTranslation; + string instanceSpecificQueue; + string sharedQueue; + IUnicastSendRouter unicastSendRouter; + + public class State + { + public string ExplicitDestination { get; set; } + public string SpecificInstance { get; set; } + + public RouteOption Option + { + get { return option; } + set + { + if (option != RouteOption.None) + { + throw new Exception("Already specified routing option for this message: " + option); + } + option = value; + } + } + + RouteOption option; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingFeature.cs b/src/NServiceBus.Core/Routing/RoutingFeature.cs new file mode 100644 index 00000000000..49752d87558 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingFeature.cs @@ -0,0 +1,130 @@ +namespace NServiceBus.Features +{ + using Config; + using Routing; + using Routing.MessageDrivenSubscriptions; + using Transport; + using Unicast.Messages; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + class RoutingFeature : Feature + { + public const string EnforceBestPracticesSettingsKey = "NServiceBus.Routing.EnforceBestPractices"; + + public RoutingFeature() + { + EnableByDefault(); + Defaults(s => + { + s.SetDefault(EnforceBestPracticesSettingsKey, true); + s.SetDefault(new UnicastRoutingTable()); + s.SetDefault(new EndpointInstances()); + s.SetDefault(new Publishers()); + s.SetDefault(new DistributionPolicy()); + s.SetDefault(new ConfiguredUnicastRoutes()); + s.SetDefault(new ConfiguredPublishers()); + }); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var canReceive = !context.Settings.GetOrDefault("Endpoint.SendOnly"); + var transportInfrastructure = context.Settings.Get(); + + var unicastRoutingTable = context.Settings.Get(); + var endpointInstances = context.Settings.Get(); + var publishers = context.Settings.Get(); + + var distributionPolicy = context.Settings.Get(); + var configuredUnicastRoutes = context.Settings.Get(); + var configuredPublishers = context.Settings.Get(); + var conventions = context.Settings.Get(); + var unicastBusConfig = context.Settings.GetConfigSection(); + + var enforceBestPractices = context.Settings.Get(EnforceBestPracticesSettingsKey); + if (enforceBestPractices) + { + EnableBestPracticeEnforcement(context); + } + + unicastBusConfig?.MessageEndpointMappings.Apply(publishers, unicastRoutingTable, transportInfrastructure.MakeCanonicalForm, conventions); + configuredUnicastRoutes.Apply(unicastRoutingTable, conventions); + configuredPublishers.Apply(publishers, conventions, enforceBestPractices); + + var outboundRoutingPolicy = transportInfrastructure.OutboundRoutingPolicy; + context.Pipeline.Register(b => + { + var unicastSendRouter = new UnicastSendRouter(unicastRoutingTable, endpointInstances, i => transportInfrastructure.ToTransportAddress(LogicalAddress.CreateRemoteAddress(i))); + return new UnicastSendRouterConnector(context.Settings.LocalAddress(), context.Settings.InstanceSpecificQueue(), unicastSendRouter, distributionPolicy, i => transportInfrastructure.ToTransportAddress(LogicalAddress.CreateRemoteAddress(i))); + }, "Determines how the message being sent should be routed"); + + context.Pipeline.Register(new UnicastReplyRouterConnector(), "Determines how replies should be routed"); + if (outboundRoutingPolicy.Publishes == OutboundRoutingType.Unicast) + { + context.Pipeline.Register(b => + { + var unicastPublishRouter = new UnicastPublishRouter(b.Build(), b.Build()); + return new UnicastPublishRouterConnector(unicastPublishRouter, distributionPolicy); + }, "Determines how the published messages should be routed"); + } + else + { + context.Pipeline.Register(new MulticastPublishRouterBehavior(), "Determines how the published messages should be routed"); + } + + if (canReceive) + { + var publicReturnAddress = context.Settings.GetOrDefault("PublicReturnAddress"); + var distributorAddress = context.Settings.GetOrDefault("LegacyDistributor.Address"); + context.Pipeline.Register(new ApplyReplyToAddressBehavior(context.Settings.LocalAddress(), context.Settings.InstanceSpecificQueue(), publicReturnAddress, distributorAddress), "Applies the public reply to address to outgoing messages"); + + if (outboundRoutingPolicy.Publishes == OutboundRoutingType.Unicast) + { + var subscriberAddress = distributorAddress ?? context.Settings.LocalAddress(); + var subscriptionRouter = new SubscriptionRouter(publishers, endpointInstances, i => transportInfrastructure.ToTransportAddress(LogicalAddress.CreateRemoteAddress(i))); + + context.Pipeline.Register(b => new MessageDrivenSubscribeTerminator(subscriptionRouter, subscriberAddress, context.Settings.EndpointName(), b.Build()), "Sends subscription requests when message driven subscriptions is in use"); + context.Pipeline.Register(b => new MessageDrivenUnsubscribeTerminator(subscriptionRouter, subscriberAddress, context.Settings.EndpointName(), b.Build()), "Sends requests to unsubscribe when message driven subscriptions is in use"); + } + else + { + var transportSubscriptionInfrastructure = transportInfrastructure.ConfigureSubscriptionInfrastructure(); + var subscriptionManager = transportSubscriptionInfrastructure.SubscriptionManagerFactory(); + + context.Pipeline.Register(new NativeSubscribeTerminator(subscriptionManager), "Requests the transport to subscribe to a given message type"); + context.Pipeline.Register(new NativeUnsubscribeTerminator(subscriptionManager), "Requests the transport to unsubscribe to a given message type"); + } + } + } + + void EnableBestPracticeEnforcement(FeatureConfigurationContext context) + { + var validations = new Validations(context.Settings.Get()); + + context.Pipeline.Register( + "EnforceSendBestPractices", + new EnforceSendBestPracticesBehavior(validations), + "Enforces send messaging best practices"); + + context.Pipeline.Register( + "EnforceReplyBestPractices", + new EnforceReplyBestPracticesBehavior(validations), + "Enforces reply messaging best practices"); + + context.Pipeline.Register( + "EnforcePublishBestPractices", + new EnforcePublishBestPracticesBehavior(validations), + "Enforces publish messaging best practices"); + + context.Pipeline.Register( + "EnforceSubscribeBestPractices", + new EnforceSubscribeBestPracticesBehavior(validations), + "Enforces subscribe messaging best practices"); + + context.Pipeline.Register( + "EnforceUnsubscribeBestPractices", + new EnforceUnsubscribeBestPracticesBehavior(validations), + "Enforces unsubscribe messaging best practices"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingFeatureSettingsExtensions.cs b/src/NServiceBus.Core/Routing/RoutingFeatureSettingsExtensions.cs new file mode 100644 index 00000000000..2466cd20bde --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingFeatureSettingsExtensions.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + /// + /// Configuration extensions for routing feature settings. + /// + public static class RoutingFeatureSettingsExtensions + { + /// + /// Sets the public return address of this endpoint. + /// + /// The endpoint configuration to extend. + /// The public return address for messages sent by this endpoint. + public static void OverridePublicReturnAddress(this EndpointConfiguration configuration, string address) + { + Guard.AgainstNullAndEmpty(nameof(address), address); + configuration.Settings.SetDefault("PublicReturnAddress", address); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingOptionExtensions.cs b/src/NServiceBus.Core/Routing/RoutingOptionExtensions.cs new file mode 100644 index 00000000000..3864d045a0e --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingOptionExtensions.cs @@ -0,0 +1,341 @@ +namespace NServiceBus +{ + /// + /// Gives users fine grained control over routing via extension methods. + /// + public static class RoutingOptionExtensions + { + /// + /// Allows a specific physical address to be used to route this message. + /// + /// Option being extended. + /// The destination address. + public static void SetDestination(this SendOptions options, string destination) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + + var state = options.Context.GetOrCreate(); + state.Option = UnicastSendRouterConnector.RouteOption.ExplicitDestination; + state.ExplicitDestination = destination; + } + + /// + /// Allows the target endpoint instance for this reply to set. If not used the reply will be sent to the `ReplyToAddress` + /// of the incoming message. + /// + /// Option being extended. + /// The new target address. + public static void SetDestination(this ReplyOptions options, string destination) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + + options.Context.GetOrCreate() + .ExplicitDestination = destination; + } + + /// + /// Returns the destination configured by . + /// + /// Option being extended. + /// The specified destination address or null when no destination was specified. + public static string GetDestination(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + UnicastReplyRouterConnector.State state; + options.Context.TryGet(out state); + return state?.ExplicitDestination; + } + + /// + /// Returns the destination configured by . + /// + /// Option being extended. + /// The specified destination address or null when no destination was specified. + public static string GetDestination(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + UnicastSendRouterConnector.State state; + options.Context.TryGet(out state); + return state?.ExplicitDestination; + } + + /// + /// Routes this message to any instance of this endpoint. + /// + /// Option being extended. + public static void RouteToThisEndpoint(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = UnicastSendRouterConnector.RouteOption.RouteToAnyInstanceOfThisEndpoint; + } + + /// + /// Returns whether the message should be routed to this endpoint. + /// + /// Option being extended. + /// true when has been called, false otherwise. + public static bool IsRoutingToThisEndpoint(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + UnicastSendRouterConnector.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == UnicastSendRouterConnector.RouteOption.RouteToAnyInstanceOfThisEndpoint; + } + + return false; + } + + /// + /// Routes this message to this endpoint instance. + /// + /// Option being extended. + public static void RouteToThisInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = UnicastSendRouterConnector.RouteOption.RouteToThisInstance; + } + + /// + /// Returns whether the message should be routed to this endpoint instance. + /// + /// Option being extended. + /// true when has been called, false otherwise. + public static bool IsRoutingToThisInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + UnicastSendRouterConnector.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == UnicastSendRouterConnector.RouteOption.RouteToThisInstance; + } + + return false; + } + + /// + /// Routes this message to a specific instance of a destination endpoint. + /// + /// Option being extended. + /// ID of destination instance. + public static void RouteToSpecificInstance(this SendOptions options, string instanceId) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNull(nameof(instanceId), instanceId); + + var state = options.Context.GetOrCreate(); + state.Option = UnicastSendRouterConnector.RouteOption.RouteToSpecificInstance; + state.SpecificInstance = instanceId; + } + + /// + /// Returns the instance configured by where the message should be routed to. + /// + /// Option being extended. + /// The configured instance ID or null when no instance was configured. + public static string GetRouteToSpecificInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + UnicastSendRouterConnector.State state; + if (options.Context.TryGet(out state) && state.Option == UnicastSendRouterConnector.RouteOption.RouteToSpecificInstance) + { + return state.SpecificInstance; + } + + return null; + } + + /// + /// Instructs the receiver to route the reply for this message to this instance. + /// + /// Option being extended. + public static void RouteReplyToThisInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = ApplyReplyToAddressBehavior.RouteOption.RouteReplyToThisInstance; + } + + /// + /// Indicates whether has been called on this options. + /// + /// Option being extended. + public static bool IsRoutingReplyToThisInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == ApplyReplyToAddressBehavior.RouteOption.RouteReplyToThisInstance; + } + + return false; + } + + /// + /// Instructs the receiver to route the reply for this message to any instance of this endpoint. + /// + /// Option being extended. + public static void RouteReplyToAnyInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = ApplyReplyToAddressBehavior.RouteOption.RouteReplyToAnyInstanceOfThisEndpoint; + } + + /// + /// Indicates whether has been called on this options. + /// + /// Option being extended. + public static bool IsRoutingReplyToAnyInstance(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == ApplyReplyToAddressBehavior.RouteOption.RouteReplyToAnyInstanceOfThisEndpoint; + } + + return false; + } + + /// + /// Instructs the receiver to route the reply for this message to this instance. + /// + /// Option being extended. + public static void RouteReplyToThisInstance(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = ApplyReplyToAddressBehavior.RouteOption.RouteReplyToThisInstance; + } + + /// + /// Indicates whether has been called on this options. + /// + /// Option being extended. + public static bool IsRoutingReplyToThisInstance(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == ApplyReplyToAddressBehavior.RouteOption.RouteReplyToThisInstance; + } + + return false; + } + + /// + /// Instructs the receiver to route the reply for this message to any instance of this endpoint. + /// + /// Option being extended. + public static void RouteReplyToAnyInstance(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + options.Context.GetOrCreate() + .Option = ApplyReplyToAddressBehavior.RouteOption.RouteReplyToAnyInstanceOfThisEndpoint; + } + + /// + /// Indicates whether has been called on this options. + /// + /// Option being extended. + public static bool IsRoutingReplyToAnyInstance(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state)) + { + return state.Option == ApplyReplyToAddressBehavior.RouteOption.RouteReplyToAnyInstanceOfThisEndpoint; + } + + return false; + } + + /// + /// Instructs the receiver to route the reply to specified address. + /// + /// Option being extended. + /// Reply destination. + public static void RouteReplyTo(this ReplyOptions options, string address) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNull(nameof(address), address); + + var state = options.Context.GetOrCreate(); + state.Option = ApplyReplyToAddressBehavior.RouteOption.ExplicitReplyDestination; + state.ExplicitDestination = address; + } + + /// + /// Returns the configured route by . + /// + /// Option being extended. + /// The configured reply to address or null when no address configured. + public static string GetReplyToRoute(this ReplyOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state) && state.Option == ApplyReplyToAddressBehavior.RouteOption.ExplicitReplyDestination) + { + return state.ExplicitDestination; + } + + return null; + } + + /// + /// Instructs the receiver to route the reply to specified address. + /// + /// Option being extended. + /// Reply destination. + public static void RouteReplyTo(this SendOptions options, string address) + { + Guard.AgainstNull(nameof(options), options); + Guard.AgainstNull(nameof(address), address); + + var state = options.Context.GetOrCreate(); + state.Option = ApplyReplyToAddressBehavior.RouteOption.ExplicitReplyDestination; + state.ExplicitDestination = address; + } + + /// + /// Returns the configured route by . + /// + /// Option being extended. + /// The configured reply to address or null when no address configured. + public static string GetReplyToRoute(this SendOptions options) + { + Guard.AgainstNull(nameof(options), options); + + ApplyReplyToAddressBehavior.State state; + if (options.Context.TryGet(out state) && state.Option == ApplyReplyToAddressBehavior.RouteOption.ExplicitReplyDestination) + { + return state.ExplicitDestination; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingSettings.cs b/src/NServiceBus.Core/Routing/RoutingSettings.cs new file mode 100644 index 00000000000..1122d000301 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingSettings.cs @@ -0,0 +1,101 @@ +namespace NServiceBus +{ + using System; + using System.Reflection; + using Configuration.AdvanceExtensibility; + using Features; + using Routing; + using Settings; + using Transport; + + /// + /// Exposes settings related to routing. + /// + public class RoutingSettings : ExposeSettings + { + /// + /// Creates a new instance of . + /// + public RoutingSettings(SettingsHolder settings) + : base(settings) + { + } + + /// + /// Adds a static unicast route for a given message type. + /// + /// The message which should be routed. + /// The destination endpoint. + public void RouteToEndpoint(Type messageType, string destination) + { + ThrowOnAddress(destination); + + Settings.GetOrCreate().Add(new TypeRouteSource(messageType, UnicastRoute.CreateFromEndpointName(destination))); + } + + /// + /// Adds a static unicast route for all types contained in the specified assembly. + /// + /// The assembly whose messages should be routed. + /// Destination endpoint. + public void RouteToEndpoint(Assembly assembly, string destination) + { + Guard.AgainstNull(nameof(assembly), assembly); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + + ThrowOnAddress(destination); + + Settings.GetOrCreate().Add(new AssemblyRouteSource(assembly, UnicastRoute.CreateFromEndpointName(destination))); + } + + /// + /// Adds a static unicast route for all types contained in the specified assembly and within the given namespace. + /// + /// The assembly whose messages should be routed. + /// The namespace of the messages which should be routed. The given value must exactly match the target namespace. + /// Destination endpoint. + public void RouteToEndpoint(Assembly assembly, string @namespace, string destination) + { + Guard.AgainstNull(nameof(assembly), assembly); + Guard.AgainstNullAndEmpty(nameof(destination), destination); + + ThrowOnAddress(destination); + + // empty namespace is null, not string.empty + @namespace = @namespace == string.Empty ? null : @namespace; + + Settings.GetOrCreate().Add(new NamespaceRouteSource(assembly, @namespace, UnicastRoute.CreateFromEndpointName(destination))); + } + + /// + /// Disables the enforcement of messaging best practices (e.g. validating that a published message is an event). + /// + public void DoNotEnforceBestPractices() + { + Settings.Set(RoutingFeature.EnforceBestPracticesSettingsKey, false); + } + + static void ThrowOnAddress(string destination) + { + if (destination.Contains("@")) + { + throw new ArgumentException($"A logical endpoint name should not contain '@', but received '{destination}'. To specify an endpoint's address, use the instance mapping file for the MSMQ transport, or refer to the routing documentation."); + } + } + } + + /// + /// Exposes settings related to routing. + /// + public class RoutingSettings : RoutingSettings + where T : TransportDefinition + { + /// + /// Creates a new instance of . + /// + public RoutingSettings(SettingsHolder settings) + : base(settings) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingSettingsExtensions.cs b/src/NServiceBus.Core/Routing/RoutingSettingsExtensions.cs new file mode 100644 index 00000000000..cd8a55f6475 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingSettingsExtensions.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using Transport; + + /// + /// Configuration extensions for routing. + /// + public static class RoutingSettingsExtensions + { + /// + /// Configures the routing. + /// + public static RoutingSettings Routing(this TransportExtensions config) + { + Guard.AgainstNull(nameof(config), config); + return new RoutingSettings(config.Settings); + } + + /// + /// Configures the routing. + /// + public static RoutingSettings Routing(this TransportExtensions config) + where T : TransportDefinition + { + Guard.AgainstNull(nameof(config), config); + return new RoutingSettings(config.Settings); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/RoutingStrategy.cs b/src/NServiceBus.Core/Routing/RoutingStrategy.cs new file mode 100644 index 00000000000..8ee4ea16078 --- /dev/null +++ b/src/NServiceBus.Core/Routing/RoutingStrategy.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Routing +{ + using System.Collections.Generic; + + /// + /// An abstraction that defines how a message is going to be routed. + /// + public abstract class RoutingStrategy + { + /// + /// Applies the routing strategy to the message. + /// + /// Message headers. + public abstract AddressTag Apply(Dictionary headers); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/SingleInstanceRoundRobinDistributionStrategy.cs b/src/NServiceBus.Core/Routing/SingleInstanceRoundRobinDistributionStrategy.cs new file mode 100644 index 00000000000..6dc45a11084 --- /dev/null +++ b/src/NServiceBus.Core/Routing/SingleInstanceRoundRobinDistributionStrategy.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.Routing +{ + using System.Threading; + + /// + /// Selects a single instance based on a round-robin scheme. + /// + public class SingleInstanceRoundRobinDistributionStrategy : DistributionStrategy + { + /// + /// Creates a new instance. + /// + /// The name of the endpoint this distribution strategy resolves instances for. + /// The scope for this strategy. + public SingleInstanceRoundRobinDistributionStrategy(string endpoint, DistributionStrategyScope scope) + : base(endpoint, scope) + { + } + + /// + /// Selects a destination instance for a message from all known addresses of a logical endpoint. + /// + public override string SelectReceiver(string[] receiverAddresses) + { + if (receiverAddresses.Length == 0) + { + return default(string); + } + var i = Interlocked.Increment(ref index); + var result = receiverAddresses[(int)(i % receiverAddresses.Length)]; + return result; + } + + // start with -1 so the index will be at 0 after the first increment. + long index = -1; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/SpecificInstanceDistributionPolicy.cs b/src/NServiceBus.Core/Routing/SpecificInstanceDistributionPolicy.cs new file mode 100644 index 00000000000..12acd07fe0a --- /dev/null +++ b/src/NServiceBus.Core/Routing/SpecificInstanceDistributionPolicy.cs @@ -0,0 +1,49 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using Routing; + + class SpecificInstanceDistributionPolicy : IDistributionPolicy + { + public SpecificInstanceDistributionPolicy(string discriminator, Func transportAddressTranslation) + { + this.discriminator = discriminator; + this.transportAddressTranslation = transportAddressTranslation; + } + + public DistributionStrategy GetDistributionStrategy(string endpointName, DistributionStrategyScope scope) + { + return new SpecificInstanceDistributionStrategy( + new EndpointInstance(endpointName, discriminator), + transportAddressTranslation, scope); + } + + readonly Func transportAddressTranslation; + string discriminator; + + class SpecificInstanceDistributionStrategy : DistributionStrategy + { + public SpecificInstanceDistributionStrategy(EndpointInstance instance, Func transportAddressTranslation, DistributionStrategyScope scope) : base(instance.Endpoint, scope) + { + this.instance = instance; + this.transportAddressTranslation = transportAddressTranslation; + } + + public override string SelectReceiver(string[] receiverAddresses) + { + var instanceAddress = transportAddressTranslation(instance); + var target = receiverAddresses.FirstOrDefault(t => t == instanceAddress); + if (target == null) + { + throw new Exception($"Specified instance with discriminator {instance.Discriminator} has not been configured in the routing tables."); + } + return target; + } + + readonly Func transportAddressTranslation; + + EndpointInstance instance; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/SubscribeContext.cs b/src/NServiceBus.Core/Routing/SubscribeContext.cs new file mode 100644 index 00000000000..e7b94f97581 --- /dev/null +++ b/src/NServiceBus.Core/Routing/SubscribeContext.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System; + using Pipeline; + + class SubscribeContext : BehaviorContext, ISubscribeContext + { + public SubscribeContext(IBehaviorContext parentContext, Type eventType, SubscribeOptions options) + : base(parentContext) + { + Guard.AgainstNull(nameof(parentContext), parentContext); + Guard.AgainstNull(nameof(eventType), eventType); + Guard.AgainstNull(nameof(options), options); + + parentContext.Extensions.Merge(options.Context); + + EventType = eventType; + } + + public Type EventType { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/SubscribeOptions.cs b/src/NServiceBus.Core/Routing/SubscribeOptions.cs new file mode 100644 index 00000000000..ded9048e951 --- /dev/null +++ b/src/NServiceBus.Core/Routing/SubscribeOptions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Allows the users to control how the subscribe is performed. + /// + public class SubscribeOptions : ExtendableOptions + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/TypeRouteSource.cs b/src/NServiceBus.Core/Routing/TypeRouteSource.cs new file mode 100644 index 00000000000..a4c2cade724 --- /dev/null +++ b/src/NServiceBus.Core/Routing/TypeRouteSource.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Routing; + + class TypeRouteSource : IRouteSource + { + Type messageType; + UnicastRoute route; + + public TypeRouteSource(Type messageType, UnicastRoute route) + { + this.messageType = messageType; + this.route = route; + } + + public IEnumerable GenerateRoutes(Conventions conventions) + { + if (!conventions.IsMessageType(messageType)) + { + throw new Exception($"Cannot configure routing for type '{messageType.FullName}' because it is not considered a message. Message types have to either implement NServiceBus.IMessage interface or match a defined message convention."); + } + yield return new RouteTableEntry(messageType, route); + } + + public RouteSourcePriority Priority => RouteSourcePriority.Type; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastAddressTag.cs b/src/NServiceBus.Core/Routing/UnicastAddressTag.cs new file mode 100644 index 00000000000..f954f0695c7 --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastAddressTag.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Routing +{ + /// + /// Represents a route directly to the specified destination. + /// + public class UnicastAddressTag : AddressTag + { + /// + /// Initializes the strategy. + /// + /// The destination. + public UnicastAddressTag(string destination) + { + Guard.AgainstNullAndEmpty(nameof(destination), destination); + Destination = destination; + } + + /// + /// The destination. + /// + public string Destination { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastPublishRouter.cs b/src/NServiceBus.Core/Routing/UnicastPublishRouter.cs new file mode 100644 index 00000000000..90988f8554d --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastPublishRouter.cs @@ -0,0 +1,69 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Extensibility; + using Routing; + using Unicast.Messages; + using Unicast.Subscriptions; + using Unicast.Subscriptions.MessageDrivenSubscriptions; + + class UnicastPublishRouter : IUnicastPublishRouter + { + public UnicastPublishRouter(MessageMetadataRegistry messageMetadataRegistry, ISubscriptionStorage subscriptionStorage) + { + this.messageMetadataRegistry = messageMetadataRegistry; + this.subscriptionStorage = subscriptionStorage; + } + + public async Task> Route(Type messageType, IDistributionPolicy distributionPolicy, ContextBag contextBag) + { + var typesToRoute = messageMetadataRegistry.GetMessageMetadata(messageType).MessageHierarchy; + + var subscribers = await GetSubscribers(contextBag, typesToRoute).ConfigureAwait(false); + + var selectedDestinations = SelectDestinationsForEachEndpoint(distributionPolicy, subscribers); + + return selectedDestinations.Select(destination => new UnicastRoutingStrategy(destination)); + } + + HashSet SelectDestinationsForEachEndpoint(IDistributionPolicy distributionPolicy, IEnumerable subscribers) + { + //Make sure we are sending only one to each transport destination. Might happen when there are multiple routing information sources. + var addresses = new HashSet(); + var destinationsByEndpoint = subscribers + .GroupBy(d => d.Endpoint, d => d); + + foreach (var group in destinationsByEndpoint) + { + if (group.Key == null) //Routing targets that do not specify endpoint name + { + //Send a message to each target as we have no idea which endpoint they represent + foreach (var subscriber in group) + { + addresses.Add(subscriber.TransportAddress); + } + } + else + { + var subscriber = distributionPolicy.GetDistributionStrategy(group.First().Endpoint, DistributionStrategyScope.Publish).SelectReceiver(group.Select(s => s.TransportAddress).ToArray()); + addresses.Add(subscriber); + } + } + + return addresses; + } + + async Task> GetSubscribers(ContextBag contextBag, Type[] typesToRoute) + { + var messageTypes = typesToRoute.Select(t => new MessageType(t)); + var subscribers = await subscriptionStorage.GetSubscriberAddressesForMessage(messageTypes, contextBag).ConfigureAwait(false); + return subscribers; + } + + MessageMetadataRegistry messageMetadataRegistry; + ISubscriptionStorage subscriptionStorage; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastRoute.cs b/src/NServiceBus.Core/Routing/UnicastRoute.cs new file mode 100644 index 00000000000..2e178fdd248 --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastRoute.cs @@ -0,0 +1,84 @@ +namespace NServiceBus.Routing +{ + /// + /// A destination of address routing. + /// + public class UnicastRoute + { + private UnicastRoute() + { + } + + /// + /// The logical endpoint name if present. + /// + public string Endpoint { get; private set; } + + /// + /// The endpoint instance if present. + /// + public EndpointInstance Instance { get; private set; } + + /// + /// The physical address if present. + /// + public string PhysicalAddress { get; private set; } + + /// + /// Creates a destination based on the name of the endpoint. + /// + /// Destination endpoint. + /// The new destination route. + public static UnicastRoute CreateFromEndpointName(string endpoint) + { + Guard.AgainstNull(nameof(endpoint), endpoint); + return new UnicastRoute + { + Endpoint = endpoint + }; + } + + /// + /// Creates a destination based on the name of the endpoint instance. + /// + /// Destination instance name. + /// The new destination route. + public static UnicastRoute CreateFromEndpointInstance(EndpointInstance instance) + { + Guard.AgainstNull(nameof(instance), instance); + return new UnicastRoute + { + Instance = instance + }; + } + + /// + /// Creates a destination based on the physical address. + /// + /// Destination physical address. + /// The new destination route. + public static UnicastRoute CreateFromPhysicalAddress(string physicalAddress) + { + Guard.AgainstNullAndEmpty(nameof(physicalAddress), physicalAddress); + return new UnicastRoute + { + PhysicalAddress = physicalAddress + }; + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() + { + if (Endpoint != null) + { + return Endpoint; + } + if (Instance != null) + { + return $"[{Instance}]"; + } + return $"<{PhysicalAddress}>"; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastRoutingStrategy.cs b/src/NServiceBus.Core/Routing/UnicastRoutingStrategy.cs new file mode 100644 index 00000000000..b9d211dbd8a --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastRoutingStrategy.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Routing +{ + using System.Collections.Generic; + + /// + /// A routing strategy for unicast routing. + /// + public class UnicastRoutingStrategy : RoutingStrategy + { + /// + /// Creates new routing strategy. + /// + public UnicastRoutingStrategy(string destination) + { + this.destination = destination; + } + + /// + /// Applies the routing strategy to the message. + /// + /// Message headers. + public override AddressTag Apply(Dictionary headers) + { + return new UnicastAddressTag(destination); + } + + string destination; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastRoutingTable.cs b/src/NServiceBus.Core/Routing/UnicastRoutingTable.cs new file mode 100644 index 00000000000..cd44676ba43 --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastRoutingTable.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.Routing +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// The unicast routing table. + /// + public class UnicastRoutingTable + { + internal UnicastRoute GetRouteFor(Type messageType) + { + UnicastRoute unicastRoute; + return routeTable.TryGetValue(messageType, out unicastRoute) + ? unicastRoute + : null; + } + + /// + /// Adds or replaces a set of routes for a given group key. The route set is identified . + /// If the method is called the first time with a given , the routes are added. + /// If the method is called with the same multiple times, the routes registered previously under this key are replaced. + /// + /// Key for the route source. + /// Group entries. + public void AddOrReplaceRoutes(string sourceKey, IList entries) + { + lock (updateLock) + { + routeGroups[sourceKey] = entries; + var newRouteTable = new Dictionary(); + foreach (var entry in routeGroups.Values.SelectMany(g => g)) + { + if (newRouteTable.ContainsKey(entry.MessageType)) + { + throw new Exception($"Route for type {entry.MessageType.FullName} already exists."); + } + newRouteTable[entry.MessageType] = entry.Route; + } + routeTable = newRouteTable; + } + } + + Dictionary routeTable = new Dictionary(); + Dictionary> routeGroups = new Dictionary>(); + object updateLock = new object(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnicastSendRouter.cs b/src/NServiceBus.Core/Routing/UnicastSendRouter.cs new file mode 100644 index 00000000000..33fadb0643a --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnicastSendRouter.cs @@ -0,0 +1,44 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using Routing; + + class UnicastSendRouter : IUnicastSendRouter + { + public UnicastSendRouter(UnicastRoutingTable unicastRoutingTable, EndpointInstances endpointInstances, Func transportAddressTranslation) + { + this.unicastRoutingTable = unicastRoutingTable; + this.endpointInstances = endpointInstances; + this.transportAddressTranslation = transportAddressTranslation; + } + + public UnicastRoutingStrategy Route(Type messageType, IDistributionPolicy distributionPolicy) + { + var route = unicastRoutingTable.GetRouteFor(messageType); + if (route == null) + { + return null; + } + + if (route.PhysicalAddress != null) + { + return new UnicastRoutingStrategy(route.PhysicalAddress); + } + + if (route.Instance != null) + { + return new UnicastRoutingStrategy(transportAddressTranslation(route.Instance)); + } + + + var instances = endpointInstances.FindInstances(route.Endpoint).Select(e => transportAddressTranslation(e)).ToArray(); + var selectedInstanceAddress = distributionPolicy.GetDistributionStrategy(route.Endpoint, DistributionStrategyScope.Send).SelectReceiver(instances); + return new UnicastRoutingStrategy(selectedInstanceAddress); + } + + EndpointInstances endpointInstances; + Func transportAddressTranslation; + UnicastRoutingTable unicastRoutingTable; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnsubscribeContext.cs b/src/NServiceBus.Core/Routing/UnsubscribeContext.cs new file mode 100644 index 00000000000..8f536d2b117 --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnsubscribeContext.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System; + using Pipeline; + + class UnsubscribeContext : BehaviorContext, IUnsubscribeContext + { + public UnsubscribeContext(IBehaviorContext parentContext, Type eventType, UnsubscribeOptions options) + : base(parentContext) + { + Guard.AgainstNull(nameof(parentContext), parentContext); + Guard.AgainstNull(nameof(eventType), eventType); + Guard.AgainstNull(nameof(options), options); + + parentContext.Extensions.Merge(options.Context); + + EventType = eventType; + } + + public Type EventType { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Routing/UnsubscribeOptions.cs b/src/NServiceBus.Core/Routing/UnsubscribeOptions.cs new file mode 100644 index 00000000000..6331c0ab069 --- /dev/null +++ b/src/NServiceBus.Core/Routing/UnsubscribeOptions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Allows the users to control how the unsubscribe is performed. + /// + public class UnsubscribeOptions : ExtendableOptions + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/ContainSagaData.cs b/src/NServiceBus.Core/Saga/ContainSagaData.cs deleted file mode 100644 index ce3f58bbc5d..00000000000 --- a/src/NServiceBus.Core/Saga/ContainSagaData.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - - /// - /// Base class to make defining saga data easier - /// - public abstract class ContainSagaData : IContainSagaData - { - /// - /// The saga id - /// - public virtual Guid Id { get; set; } - - /// - /// The address io the endpoint that started the saga - /// - public virtual string Originator { get; set; } - - /// - /// The id of the message that started the saga - /// - public virtual string OriginalMessageId { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/IFindSagas.cs b/src/NServiceBus.Core/Saga/IFindSagas.cs deleted file mode 100644 index 5aeda2e01bf..00000000000 --- a/src/NServiceBus.Core/Saga/IFindSagas.cs +++ /dev/null @@ -1,21 +0,0 @@ - -namespace NServiceBus.Saga -{ - /// - /// Interface indicating that implementers can find sagas of the given type. - /// - public abstract class IFindSagas where T : IContainSagaData - { - /// - /// Narrower interface indicating that implementers can find sagas - /// of type T using messages of type M. - /// - public interface Using : IFinder - { - /// - /// Finds a saga entity of the type T using a message of type M. - /// - T FindBy(M message); - } - } -} diff --git a/src/NServiceBus.Core/Saga/IFinder.cs b/src/NServiceBus.Core/Saga/IFinder.cs deleted file mode 100644 index 5acb3988aac..00000000000 --- a/src/NServiceBus.Core/Saga/IFinder.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.Saga -{ - /// - /// Marker interface for - /// - public interface IFinder { } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/IHandleSagaNotFound.cs b/src/NServiceBus.Core/Saga/IHandleSagaNotFound.cs deleted file mode 100644 index 63588bd142a..00000000000 --- a/src/NServiceBus.Core/Saga/IHandleSagaNotFound.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus.Saga -{ - /// - /// Implementors will be invoked when a message arrives that should have been processed - /// by a saga, but no existing saga was found. This does not include the scenario when - /// a saga will be created for the given message type. - /// - public interface IHandleSagaNotFound - { - /// - /// Implementors will implement this method, likely using an injected IBus - /// to send responses to the client who sent the message. - /// - void Handle(object message); - } -} diff --git a/src/NServiceBus.Core/Saga/IHandleTimeouts.cs b/src/NServiceBus.Core/Saga/IHandleTimeouts.cs deleted file mode 100644 index 453067967ad..00000000000 --- a/src/NServiceBus.Core/Saga/IHandleTimeouts.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.Saga -{ - /// - /// Tells the infrastructure that the user wants to handle a timeout of T - /// - public interface IHandleTimeouts - { - /// - /// Called when the timeout has expired - /// - void Timeout(T state); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/ISagaPersister.cs b/src/NServiceBus.Core/Saga/ISagaPersister.cs deleted file mode 100644 index 37055fe4c09..00000000000 --- a/src/NServiceBus.Core/Saga/ISagaPersister.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - - /// - /// Defines the basic functionality of a persister for storing - /// and retrieving a saga. - /// - public interface ISagaPersister - { - /// - /// Saves the saga entity to the persistence store. - /// - /// The saga entity to save. - void Save(IContainSagaData saga); - - /// - /// Updates an existing saga entity in the persistence store. - /// - /// The saga entity to updated. - void Update(IContainSagaData saga); - - /// - /// Gets a saga entity from the persistence store by its Id. - /// - /// The Id of the saga entity to get. - TSagaData Get(Guid sagaId) where TSagaData : IContainSagaData; - - /// - /// Looks up a saga entity by a given property. - /// - TSagaData Get(string propertyName, object propertyValue) where TSagaData : IContainSagaData; - - /// - /// Sets a saga as completed and removes it from the active saga list - /// in the persistence store. - /// - /// The saga to complete. - void Complete(IContainSagaData saga); - } - -} diff --git a/src/NServiceBus.Core/Saga/Obsoletes.cs b/src/NServiceBus.Core/Saga/Obsoletes.cs deleted file mode 100644 index d1776aa0c4c..00000000000 --- a/src/NServiceBus.Core/Saga/Obsoletes.cs +++ /dev/null @@ -1,45 +0,0 @@ -#pragma warning disable 1591 -namespace NServiceBus.Saga -{ - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "NServiceBus.Saga.Saga")] - public interface ISaga - { - } - - [ObsoleteEx(RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "NServiceBus.Saga.Saga")] - public interface ISaga - { - } - - [ObsoleteEx( - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "ISagaPersister")] - public interface IPersistSagas - { - } - - [ObsoleteEx( - Message = "Since `ISaga` has been merged into the abstract class `Saga` this interface is no longer required.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "NServiceBus.Saga.Saga")] - public interface IConfigurable - { - } - - [ObsoleteEx( - Message = "Since `ISaga` has been merged into the abstract class `Saga` this interface is no longer required.", - RemoveInVersion = "6", - TreatAsErrorFromVersion = "5", - Replacement = "NServiceBus.Saga.Saga.Completed")] - public interface HasCompleted - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/Saga.cs b/src/NServiceBus.Core/Saga/Saga.cs deleted file mode 100644 index ddd3e0207e6..00000000000 --- a/src/NServiceBus.Core/Saga/Saga.cs +++ /dev/null @@ -1,190 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - - /// - /// This class is used to define sagas containing data and handling a message. - /// To handle more message types, implement - /// for the relevant types. - /// To signify that the receipt of a message should start this saga, - /// implement for the relevant message type. - /// - public abstract class Saga - { - /// - /// The saga's typed data. - /// - public IContainSagaData Entity{get; set; } - - /// - /// Override this method in order to configure how this saga's data should be found. - /// - internal protected abstract void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration); - - /// - /// Bus object used for retrieving the sender endpoint which caused this saga to start. - /// Necessary for . - /// - public IBus Bus - { - get - { - if (bus == null) - throw new InvalidOperationException("No IBus instance available, please configure one and also verify that you're not defining your own Bus property in your saga since that hides the one in the base class"); - - return bus; - } - set { bus = value; } - } - - IBus bus; - - /// - /// Indicates that the saga is complete. - /// In order to set this value, use the method. - /// - public bool Completed { get; private set; } - - /// - /// Request for a timeout to occur at the given . - /// - /// to send timeout . - protected void RequestTimeout(DateTime at) where TTimeoutMessageType : new() - { - RequestTimeout(at, new TTimeoutMessageType()); - } - - /// - /// Request for a timeout to occur at the given . - /// - /// to send call . - /// Callback to execute after is reached. - [ObsoleteEx( - Message = "Construct your message and pass it to the non Action overload.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "Saga.RequestTimeout(DateTime, TTimeoutMessageType)")] - protected void RequestTimeout(DateTime at, Action action) where TTimeoutMessageType : new() - { - var instance = new TTimeoutMessageType(); - action(instance); - RequestTimeout(at, instance); - } - - /// - /// Request for a timeout to occur at the given . - /// - /// to send timeout . - /// The message to send after is reached. - protected void RequestTimeout(DateTime at, TTimeoutMessageType timeoutMessage) - { - if (at.Kind == DateTimeKind.Unspecified) - { - throw new InvalidOperationException("Kind property of DateTime 'at' must be specified."); - } - - VerifySagaCanHandleTimeout(timeoutMessage); - SetTimeoutHeaders(timeoutMessage); - - Bus.Defer(at, timeoutMessage); - } - - void VerifySagaCanHandleTimeout(TTimeoutMessageType timeoutMessage) - { - var canHandleTimeoutMessage = this is IHandleTimeouts; - if (!canHandleTimeoutMessage) - { - var message = string.Format("The type '{0}' cannot request timeouts for '{1}' because it does not implement 'IHandleTimeouts<{2}>'", GetType().Name, timeoutMessage, typeof(TTimeoutMessageType).FullName); - throw new Exception(message); - } - } - - /// - /// Request for a timeout to occur within the give . - /// - /// Given to delay timeout message by. - protected void RequestTimeout(TimeSpan within) where TTimeoutMessageType : new() - { - RequestTimeout(within, new TTimeoutMessageType()); - } - - /// - /// Request for a timeout to occur within the give . - /// - /// Given to delay timeout message by. - /// An which initializes properties of the message that is sent after expires. - [ObsoleteEx( - Message = "Construct your message and pass it to the non Action overload.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "Saga.RequestTimeout(TimeSpan, TTimeoutMessageType)")] - protected void RequestTimeout(TimeSpan within, Action messageConstructor) where TTimeoutMessageType : new() - { - var instance = new TTimeoutMessageType(); - messageConstructor(instance); - RequestTimeout(within, instance); - } - - /// - /// Request for a timeout to occur within the given . - /// - /// Given to delay timeout message by. - /// The message to send after expires. - protected void RequestTimeout(TimeSpan within, TTimeoutMessageType timeoutMessage) - { - VerifySagaCanHandleTimeout(timeoutMessage); - SetTimeoutHeaders(timeoutMessage); - - Bus.Defer(within, timeoutMessage); - } - - - void SetTimeoutHeaders(object toSend) - { - Bus.SetMessageHeader(toSend, Headers.SagaId, Entity.Id.ToString()); - Bus.SetMessageHeader(toSend, Headers.IsSagaTimeoutMessage, Boolean.TrueString); - Bus.SetMessageHeader(toSend, Headers.SagaType, GetType().AssemblyQualifiedName); - } - - /// - /// Sends the using the bus to the endpoint that caused this saga to start. - /// - protected virtual void ReplyToOriginator(object message) - { - if (string.IsNullOrEmpty(Entity.Originator)) - { - throw new Exception("Entity.Originator cannot be null. Perhaps the sender is a SendOnly endpoint."); - } - Bus.SetMessageHeader(message, "$.temporary.ReplyToOriginator", "ReplyToOriginator"); // issue 2748 - Bus.Send(Entity.Originator, Entity.OriginalMessageId, message); - } - - /// - /// Instantiates a message of the given type, setting its properties using the given action, - /// and sends it using the bus to the endpoint that caused this saga to start. - /// - /// The type of message to construct. - /// An which initializes properties of the message reply with. - [ObsoleteEx( - Message = "Construct your message and pass it to the non Action overload.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "6.0", - Replacement = "Saga.ReplyToOriginator(object)")] - protected virtual void ReplyToOriginator(Action messageConstructor) where TMessage : new() - { - var instance = new TMessage(); - messageConstructor(instance); - ReplyToOriginator(instance); - } - - /// - /// Marks the saga as complete. - /// This may result in the sagas state being deleted by the persister. - /// - protected virtual void MarkAsComplete() - { - Completed = true; - } - - } -} diff --git a/src/NServiceBus.Core/Saga/SagaPropertyMapper.cs b/src/NServiceBus.Core/Saga/SagaPropertyMapper.cs deleted file mode 100644 index aec388b8c5c..00000000000 --- a/src/NServiceBus.Core/Saga/SagaPropertyMapper.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - using System.Linq.Expressions; - - /// - /// A helper class that proved syntactical sugar as part of - /// - /// A type that implements . - public class SagaPropertyMapper where TSagaData : IContainSagaData - { - IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration; - - internal SagaPropertyMapper(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) - { - this.sagaMessageFindingConfiguration = sagaMessageFindingConfiguration; - } - - /// - /// Specify how to map between and . - /// - /// The message type to map to. - /// An that represents the message. - /// A that provides the fluent chained to link with . - public ToSagaExpression ConfigureMapping(Expression> messageProperty) - { - return new ToSagaExpression(sagaMessageFindingConfiguration, messageProperty); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/SagaT.cs b/src/NServiceBus.Core/Saga/SagaT.cs deleted file mode 100644 index 845349f855c..00000000000 --- a/src/NServiceBus.Core/Saga/SagaT.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus.Saga -{ - - /// - /// This class is used to define sagas containing data and handling a message. - /// To handle more message types, implement - /// for the relevant types. - /// To signify that the receipt of a message should start this saga, - /// implement for the relevant message type. - /// - /// A type that implements . - public abstract class Saga : Saga where TSagaData : IContainSagaData, new() - { - /// - /// The saga's strongly typed data. Wraps . - /// - public TSagaData Data - { - get { return (TSagaData)Entity; } - set { Entity = value; } - } - - - /// - /// Override this method in order to configure how this saga's data should be found. - /// - /// Override and forwards it to the generic version - internal protected override void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) - { - ConfigureHowToFindSaga(new SagaPropertyMapper(sagaMessageFindingConfiguration)); - } - - /// - /// A generic version of wraps in a generic helper class () to provide mappings specific to . - /// - /// The that wraps the - protected abstract void ConfigureHowToFindSaga(SagaPropertyMapper mapper); - } -} diff --git a/src/NServiceBus.Core/Saga/ToSagaExpression.cs b/src/NServiceBus.Core/Saga/ToSagaExpression.cs deleted file mode 100644 index 4251561f81f..00000000000 --- a/src/NServiceBus.Core/Saga/ToSagaExpression.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - using System.Linq.Expressions; - - /// - /// Allows a more fluent way to map sagas - /// - public class ToSagaExpression where TSagaData : IContainSagaData - { - IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration; - Expression> messageProperty; - - /// - /// Constructs the expression - /// - public ToSagaExpression(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration, Expression> messageProperty) - { - this.sagaMessageFindingConfiguration = sagaMessageFindingConfiguration; - this.messageProperty = messageProperty; - } - - - /// - /// Defines the property on the saga data to which the message property should be mapped - /// - /// The property to map - public void ToSaga(Expression> sagaEntityProperty) - { - sagaMessageFindingConfiguration.ConfigureMapping(sagaEntityProperty, messageProperty); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/UniqueAttribute.cs b/src/NServiceBus.Core/Saga/UniqueAttribute.cs deleted file mode 100644 index 48167281c66..00000000000 --- a/src/NServiceBus.Core/Saga/UniqueAttribute.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace NServiceBus.Saga -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - - /// - /// Used to specify that a saga property should be unique across all saga instances. - /// This will ensure that 2 saga instances don't get persisted when using the property to correlate between multiple message types - /// - [AttributeUsage(AttributeTargets.Property)] - public sealed class UniqueAttribute : Attribute - { - /// - /// Gets a single property that is marked with the for a . - /// - /// The to evaluate. - /// A of the property marked with a or null if not used. - public static PropertyInfo GetUniqueProperty(Type type) - { - var properties = GetUniqueProperties(type) - .ToList(); - - if (properties.Count > 1) - { - var message = string.Format("More than one UniqueAttribute property was found on the type '{0}'. However, only one property is supported.", type.FullName); - throw new InvalidOperationException(message); - } - - return properties.SingleOrDefault(); - } - - /// - /// Gets a single property that is marked with the for a . - /// - /// A saga entity. - /// A of the property marked with a or null if not used. - public static KeyValuePair? GetUniqueProperty(IContainSagaData entity) - { - var prop = GetUniqueProperty(entity.GetType()); - - return prop != null ? - new KeyValuePair(prop.Name, prop.GetValue(entity, null)) : - (KeyValuePair?) null; - } - - /// - /// Gets all the properties that are marked with the for a . - /// - /// A . - /// A of property names and their values. - public static IDictionary GetUniqueProperties(IContainSagaData entity) - { - return GetUniqueProperties(entity.GetType()) - .ToDictionary(p => p.Name, p => p.GetValue(entity, null)); - } - - /// - /// Gets all the properties that are marked with the for the given . - /// - /// The type to evaluate. - /// A of . - public static IEnumerable GetUniqueProperties(Type type) - { - return type.GetProperties() - .Where(p => p.CanRead && GetCustomAttribute(p, typeof(UniqueAttribute)) != null); - } - } -} diff --git a/src/NServiceBus.Core/Sagas/ActiveSagaInstance.cs b/src/NServiceBus.Core/Sagas/ActiveSagaInstance.cs index 2c8a35da1af..8df097031d2 100644 --- a/src/NServiceBus.Core/Sagas/ActiveSagaInstance.cs +++ b/src/NServiceBus.Core/Sagas/ActiveSagaInstance.cs @@ -1,61 +1,89 @@ namespace NServiceBus.Sagas { using System; - using Saga; + using System.Linq; + using System.Reflection; /// - /// Represents a saga instance being processed on the pipeline + /// Represents a saga instance being processed on the pipeline. /// public class ActiveSagaInstance { - Guid sagaId; - internal ActiveSagaInstance(Saga saga,SagaMetadata metadata) + readonly Func currentUtcDateTimeProvider; + + internal ActiveSagaInstance(Saga saga, SagaMetadata metadata, Func currentUtcDateTimeProvider) { + this.currentUtcDateTimeProvider = currentUtcDateTimeProvider; Instance = saga; Metadata = metadata; + + Created = currentUtcDateTimeProvider(); + Modified = Created; } /// - /// The id of the saga + /// The id of the saga. /// public string SagaId { get; private set; } /// - /// The type of the saga + /// The type of the saga. /// - [ObsoleteEx(TreatAsErrorFromVersion = "6", RemoveInVersion = "7", Replacement = ".Metadata.SagaType")] - public Type SagaType + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = ".Metadata.SagaType")] + public Type SagaType { - get { return Metadata.SagaType; } + get { throw new NotImplementedException(); } } /// - /// Metadata for this active saga + /// Metadata for this active saga. /// - internal SagaMetadata Metadata { get; private set; } - + internal SagaMetadata Metadata { get; } + /// - /// The actual saga instance + /// The actual saga instance. /// - [ObsoleteEx(TreatAsErrorFromVersion = "6", RemoveInVersion = "7", Replacement = "context.MessageHandler.Instance")] - public Saga Instance { get; private set; } - + public Saga Instance { get; } + /// - /// True if this saga was created by this incoming message + /// True if this saga was created by this incoming message. /// public bool IsNew { get; private set; } - + /// - /// True if no saga instance could be found for this message + /// True if no saga instance could be found for this message. /// public bool NotFound { get; private set; } /// - /// Provides a way to update the actual saga entity + /// UTC timestamp of when the active saga instance was created. /// - /// The new entity + public DateTime Created { get; } + + /// + /// UTC timestamp of when the active saga instance was last modified. + /// + public DateTime Modified { get; private set; } + + + internal bool TryGetCorrelationProperty(out CorrelationPropertyInfo sagaCorrelationProperty) + { + sagaCorrelationProperty = correlationProperty; + + + return sagaCorrelationProperty != null; + } + + /// + /// Provides a way to update the actual saga entity. + /// + /// The new entity. public void AttachNewEntity(IContainSagaData sagaEntity) { + Guard.AgainstNull(nameof(sagaEntity), sagaEntity); IsNew = true; AttachEntity(sagaEntity); } @@ -68,20 +96,132 @@ internal void AttachExistingEntity(IContainSagaData loadedEntity) void AttachEntity(IContainSagaData sagaEntity) { sagaId = sagaEntity.Id; + UpdateModified(); Instance.Entity = sagaEntity; SagaId = sagaEntity.Id.ToString(); + + var properties = sagaEntity.GetType().GetProperties(); + SagaMetadata.CorrelationPropertyMetadata correlatedPropertyMetadata; + + if (Metadata.TryGetCorrelationProperty(out correlatedPropertyMetadata)) + { + var propertyInfo = properties.Single(p => p.Name == correlatedPropertyMetadata.Name); + var propertyValue = propertyInfo.GetValue(sagaEntity); + + var defaultValue = GetDefault(propertyInfo.PropertyType); + + var hasValue = propertyValue != null && !propertyValue.Equals(defaultValue); + + correlationProperty = new CorrelationPropertyInfo + { + PropertyInfo = propertyInfo, + Value = propertyValue, + HasExistingValue = hasValue + }; + } } + + void UpdateModified() + { + Modified = currentUtcDateTimeProvider(); + } + internal void MarkAsNotFound() { NotFound = true; + UpdateModified(); } - internal void ValidateIdHasNotChanged() + internal void Completed() + { + UpdateModified(); + } + + internal void Updated() + { + UpdateModified(); + } + + internal void ValidateChanges() + { + ValidateSagaIdIsNotModified(); + + if (IsNew) + { + ValidateCorrelationPropertyHaveValue(); + } + + ValidateCorrelationPropertyNotModified(); + } + + void ValidateCorrelationPropertyHaveValue() + { + if (correlationProperty == null) + { + return; + } + + var defaultValue = GetDefault(correlationProperty.PropertyInfo.PropertyType); + + if (!correlationProperty.Value.Equals(defaultValue)) + { + return; + } + + throw new Exception( + $@"The correlated property '{correlationProperty.PropertyInfo.Name}' on saga '{Metadata.SagaType.Name}' does not have a value. +A correlated property must have a non default (i.e. non null and non-empty) value assigned when a new saga instance is created."); + } + + void ValidateCorrelationPropertyNotModified() + { + if (correlationProperty == null) + { + return; + } + + if (!correlationProperty.HasExistingValue) + { + return; + } + + var currentValue = correlationProperty.PropertyInfo.GetValue(Instance.Entity); + + if (correlationProperty.Value.ToString() == currentValue.ToString()) + { + return; + } + + throw new Exception( + $@"The value of the correlated property '{correlationProperty.PropertyInfo.Name}' on saga '{Metadata.SagaType.Name}' has changed from '{correlationProperty.Value}' to '{currentValue}'. +Changing the value of correlated properties at runtime is currently not supported."); + } + + void ValidateSagaIdIsNotModified() { if (sagaId != Instance.Entity.Id) { - throw new Exception("A modification of IContainSagaData.Id has been detected. This property is for infrastructure purposes only and should not be modified. SagaType: " + SagaType.FullName); + throw new Exception("A modification of IContainSagaData.Id has been detected. This property is for infrastructure purposes only and should not be modified. SagaType: " + Metadata.SagaType.FullName); + } + } + + static object GetDefault(Type type) + { + if (type.IsValueType) + { + return Activator.CreateInstance(type); } + return null; + } + + CorrelationPropertyInfo correlationProperty; + Guid sagaId; + + internal class CorrelationPropertyInfo + { + public bool HasExistingValue; + public PropertyInfo PropertyInfo; + public object Value; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/AttachSagaDetailsToOutGoingMessageBehavior.cs b/src/NServiceBus.Core/Sagas/AttachSagaDetailsToOutGoingMessageBehavior.cs new file mode 100644 index 00000000000..0a6e09a8328 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/AttachSagaDetailsToOutGoingMessageBehavior.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + using Sagas; + + class AttachSagaDetailsToOutGoingMessageBehavior : IBehavior + { + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + ActiveSagaInstance saga; + + //attach the current saga details to the outgoing headers for correlation + if (context.Extensions.TryGet(out saga) && HasBeenFound(saga) && !string.IsNullOrEmpty(saga.SagaId)) + { + context.Headers[Headers.OriginatingSagaId] = saga.SagaId; + context.Headers[Headers.OriginatingSagaType] = saga.Metadata.SagaType.AssemblyQualifiedName; + } + + return next(context); + } + + + static bool HasBeenFound(ActiveSagaInstance saga) + { + return !saga.NotFound; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/AutoCorrelationFeature.cs b/src/NServiceBus.Core/Sagas/AutoCorrelationFeature.cs new file mode 100644 index 00000000000..a9408a1a8a2 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/AutoCorrelationFeature.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Features +{ + class AutoCorrelationFeature : Feature + { + public AutoCorrelationFeature() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + context.Pipeline.Register("PopulateAutoCorrelationHeadersForReplies", new PopulateAutoCorrelationHeadersForRepliesBehavior(), "Copies existing saga headers from incoming message to outgoing message to facilitate the auto correlation in the saga, when replying to a message that was sent by a saga."); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/ContainSagaData.cs b/src/NServiceBus.Core/Sagas/ContainSagaData.cs new file mode 100644 index 00000000000..ea7e41f9f04 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/ContainSagaData.cs @@ -0,0 +1,25 @@ +namespace NServiceBus +{ + using System; + + /// + /// Base class to make defining saga data easier. + /// + public abstract class ContainSagaData : IContainSagaData + { + /// + /// The saga id. + /// + public virtual Guid Id { get; set; } + + /// + /// The address io the endpoint that started the saga. + /// + public virtual string Originator { get; set; } + + /// + /// The id of the message that started the saga. + /// + public virtual string OriginalMessageId { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/CorrelationProperty.cs b/src/NServiceBus.Core/Sagas/CorrelationProperty.cs deleted file mode 100644 index 8c3de916033..00000000000 --- a/src/NServiceBus.Core/Sagas/CorrelationProperty.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.Sagas -{ - /// - /// Details about a saga data property used to correlate messages hitting the saga - /// - class CorrelationProperty - { - /// - /// The name of the saga data property - /// - public string Name; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/CustomFinderAdapter.cs b/src/NServiceBus.Core/Sagas/CustomFinderAdapter.cs index d4574673af7..bbb262dfc14 100644 --- a/src/NServiceBus.Core/Sagas/CustomFinderAdapter.cs +++ b/src/NServiceBus.Core/Sagas/CustomFinderAdapter.cs @@ -1,19 +1,24 @@ -namespace NServiceBus.Sagas +namespace NServiceBus { using System; - using NServiceBus.ObjectBuilder; - using NServiceBus.Saga; - using NServiceBus.Unicast.Messages; + using System.Threading.Tasks; + using Extensibility; + using ObjectBuilder; + using Persistence; + using Sagas; - class CustomFinderAdapter : SagaFinder where TSagaData : IContainSagaData + class CustomFinderAdapter : SagaFinder where TSagaData : IContainSagaData { - internal override IContainSagaData Find(IBuilder builder,SagaFinderDefinition finderDefinition, LogicalMessage message) + public override async Task Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, object message) { - var customFinderType = (Type)finderDefinition.Properties["custom-finder-clr-type"]; + var customFinderType = (Type) finderDefinition.Properties["custom-finder-clr-type"]; - var finder = (IFindSagas.Using)builder.Build(customFinderType); + var finder = (IFindSagas.Using) builder.Build(customFinderType); - return finder.FindBy((TMessage) message.Instance); + return await finder + .FindBy((TMessage) message, storageSession, context) + .ThrowIfNull() + .ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/IAmStartedByMessages.cs b/src/NServiceBus.Core/Sagas/IAmStartedByMessages.cs similarity index 84% rename from src/NServiceBus.Core/Saga/IAmStartedByMessages.cs rename to src/NServiceBus.Core/Sagas/IAmStartedByMessages.cs index ec87d4e06a3..eb351393909 100644 --- a/src/NServiceBus.Core/Saga/IAmStartedByMessages.cs +++ b/src/NServiceBus.Core/Sagas/IAmStartedByMessages.cs @@ -1,12 +1,13 @@ -namespace NServiceBus.Saga +namespace NServiceBus { + using Sagas; /// /// Use this interface to signify that when a message of the given type is - /// received, if a saga cannot be found by an + /// received, if a saga cannot be found by an /// the saga will be created. /// public interface IAmStartedByMessages : IHandleMessages { } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Saga/IConfigureHowToFindSagaWithMessage.cs b/src/NServiceBus.Core/Sagas/IConfigureHowToFindSagaWithMessage.cs similarity index 86% rename from src/NServiceBus.Core/Saga/IConfigureHowToFindSagaWithMessage.cs rename to src/NServiceBus.Core/Sagas/IConfigureHowToFindSagaWithMessage.cs index 154324b640b..712bd01d120 100644 --- a/src/NServiceBus.Core/Saga/IConfigureHowToFindSagaWithMessage.cs +++ b/src/NServiceBus.Core/Sagas/IConfigureHowToFindSagaWithMessage.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Saga +namespace NServiceBus { using System; using System.Linq.Expressions; @@ -6,13 +6,13 @@ namespace NServiceBus.Saga /// /// Implementation provided by the infrastructure - don't implement this /// or register implementations of it in the container unless you intend - /// to substantially change the way sagas work in nServiceBus. + /// to substantially change the way sagas work. /// public interface IConfigureHowToFindSagaWithMessage { /// - /// Specify that when the infrastructure is handling a message - /// of the given type, which message property should be matched to + /// Specify that when the infrastructure is handling a message + /// of the given type, which message property should be matched to /// which saga entity property in the persistent saga store. /// void ConfigureMapping(Expression> sagaEntityProperty, Expression> messageProperty) where TSagaEntity : IContainSagaData; diff --git a/src/NServiceBus.Core/Saga/IContainSagaData.cs b/src/NServiceBus.Core/Sagas/IContainSagaData.cs similarity index 95% rename from src/NServiceBus.Core/Saga/IContainSagaData.cs rename to src/NServiceBus.Core/Sagas/IContainSagaData.cs index e0678e7e00b..8be8273f2b1 100644 --- a/src/NServiceBus.Core/Saga/IContainSagaData.cs +++ b/src/NServiceBus.Core/Sagas/IContainSagaData.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Saga +namespace NServiceBus { using System; @@ -8,7 +8,7 @@ namespace NServiceBus.Saga public interface IContainSagaData { /// - /// Gets/sets the Id of the process. Do NOT generate this value in your code. + /// Gets/sets the Id of the process. Do NOT generate this value in saga code. /// The value of the Id will be generated automatically to provide the /// best performance for saving in a database. /// diff --git a/src/NServiceBus.Core/Sagas/IFindSagas.cs b/src/NServiceBus.Core/Sagas/IFindSagas.cs new file mode 100644 index 00000000000..4dbd7b89ed5 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/IFindSagas.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Sagas +{ + using System.Threading.Tasks; + using Extensibility; + using Persistence; + + /// + /// Interface indicating that implementers can find sagas of the given type. + /// + public abstract class IFindSagas where T : IContainSagaData + { + /// + /// Narrower interface indicating that implementers can find sagas + /// of type T using messages of type M. + /// + public interface Using : IFinder + { + /// + /// Finds a saga entity of the type T using a message of type M. + /// + /// This exception will be thrown if null is returned. Return a Task<T> or mark the method as async. + Task FindBy(M message, SynchronizedStorageSession storageSession, ReadOnlyContextBag context); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/IFinder.cs b/src/NServiceBus.Core/Sagas/IFinder.cs new file mode 100644 index 00000000000..941e1626b53 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/IFinder.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Sagas +{ + /// + /// Marker interface for . + /// + public interface IFinder + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/IHandleSagaNotFound.cs b/src/NServiceBus.Core/Sagas/IHandleSagaNotFound.cs new file mode 100644 index 00000000000..e37024bf13e --- /dev/null +++ b/src/NServiceBus.Core/Sagas/IHandleSagaNotFound.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Sagas +{ + using System.Threading.Tasks; + + /// + /// Implementations will be invoked when a message arrives that should have been processed + /// by a saga, but no existing saga was found. This does not include the scenario when + /// a saga will be created for the given message type. + /// + public interface IHandleSagaNotFound + { + /// + /// Implementations will implement this method, likely using an injected IBus + /// to send responses to the client who sent the message. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task Handle(object message, IMessageProcessingContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/IHandleTimeouts.cs b/src/NServiceBus.Core/Sagas/IHandleTimeouts.cs new file mode 100644 index 00000000000..327055e0298 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/IHandleTimeouts.cs @@ -0,0 +1,18 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using JetBrains.Annotations; + + /// + /// Tells the infrastructure that the user wants to handle a timeout of . + /// + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public interface IHandleTimeouts + { + /// + /// Called when the timeout has expired. + /// + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task Timeout(T state, IMessageHandlerContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/ISagaPersister.cs b/src/NServiceBus.Core/Sagas/ISagaPersister.cs new file mode 100644 index 00000000000..367d947afe7 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/ISagaPersister.cs @@ -0,0 +1,57 @@ +namespace NServiceBus.Sagas +{ + using System; + using System.Threading.Tasks; + using Extensibility; + using Persistence; + + /// + /// Defines the basic functionality of a persister for storing + /// and retrieving a sagaData. + /// + public interface ISagaPersister + { + /// + /// Saves the sagaData entity to the persistence store. + /// + /// The sagaData data to save. + /// The property to correlate. Can be null. + /// Storage session. + /// The current pipeline context. + Task Save(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SynchronizedStorageSession session, ContextBag context); + + /// + /// Updates an existing sagaData entity in the persistence store. + /// + /// The sagaData data to updated. + /// The session. + /// The current pipeline context. + Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context); + + /// + /// Gets a sagaData entity from the persistence store by its Id. + /// + /// The Id of the sagaData data to get. + /// The session. + /// The current pipeline context. + Task Get(Guid sagaId, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData; + + /// + /// Looks up a sagaData entity by a given property. + /// + /// From the data store, analyze this property. + /// From the data store, look for this value in the identified property. + /// The session. + /// The current pipeline context. + Task Get(string propertyName, object propertyValue, SynchronizedStorageSession session, ContextBag context) where TSagaData : IContainSagaData; + + /// + /// Sets a sagaData as completed and removes it from the active sagaData list + /// in the persistence store. + /// + /// The sagaData to complete. + /// The session. + /// The current pipeline context. + Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs b/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs new file mode 100644 index 00000000000..bf10ab2bf91 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs @@ -0,0 +1,38 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Logging; + using Pipeline; + using Sagas; + + class InvokeSagaNotFoundBehavior : IBehavior + { + public async Task Invoke(IIncomingLogicalMessageContext context, Func next) + { + var invocationResult = new SagaInvocationResult(); + context.Extensions.Set(invocationResult); + + await next(context).ConfigureAwait(false); + + if (invocationResult.WasFound) + { + return; + } + + logger.InfoFormat("Could not find a started saga for '{0}' message type. Going to invoke SagaNotFoundHandlers.", context.Message.MessageType.FullName); + + foreach (var handler in context.Builder.BuildAll()) + { + logger.DebugFormat("Invoking SagaNotFoundHandler ('{0}')", handler.GetType().FullName); + + await handler + .Handle(context.Message.Instance, context) + .ThrowIfNull() + .ConfigureAwait(false); + } + } + + static ILog logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/LoadSagaByIdWrapper.cs b/src/NServiceBus.Core/Sagas/LoadSagaByIdWrapper.cs index cc0183b800b..333abf324e5 100644 --- a/src/NServiceBus.Core/Sagas/LoadSagaByIdWrapper.cs +++ b/src/NServiceBus.Core/Sagas/LoadSagaByIdWrapper.cs @@ -1,20 +1,17 @@ -namespace NServiceBus.Sagas +namespace NServiceBus { using System; - using NServiceBus.Saga; + using System.Threading.Tasks; + using Extensibility; + using Persistence; + using Sagas; - //this class in only here until we can move to a better saga persister api - class LoadSagaByIdWrapper:SagaLoader where T : IContainSagaData + class LoadSagaByIdWrapper : SagaLoader where T : IContainSagaData { - public IContainSagaData Load(ISagaPersister persister, string sagaId) + public async Task Load(ISagaPersister persister, string sagaId, SynchronizedStorageSession storageSession, ContextBag context) { - return persister.Get(Guid.Parse(sagaId)); + return await persister.Get(Guid.Parse(sagaId), storageSession, context).ConfigureAwait(false); } } - - interface SagaLoader - { - IContainSagaData Load(ISagaPersister persister, string sagaId); - } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/PopulateAutoCorrelationHeadersForRepliesBehavior.cs b/src/NServiceBus.Core/Sagas/PopulateAutoCorrelationHeadersForRepliesBehavior.cs index 4c4abb68d04..d82cec21e2f 100644 --- a/src/NServiceBus.Core/Sagas/PopulateAutoCorrelationHeadersForRepliesBehavior.cs +++ b/src/NServiceBus.Core/Sagas/PopulateAutoCorrelationHeadersForRepliesBehavior.cs @@ -1,49 +1,58 @@ namespace NServiceBus { using System; - using NServiceBus.Sagas; + using System.Threading.Tasks; using Pipeline; - using Pipeline.Contexts; - using Unicast; - using Unicast.Transport; + using Transport; - - class PopulateAutoCorrelationHeadersForRepliesBehavior : IBehavior + class PopulateAutoCorrelationHeadersForRepliesBehavior : IBehavior { - public void Invoke(OutgoingContext context, Action next) + public Task Invoke(IOutgoingReplyContext context, Func next) { - if (context.OutgoingLogicalMessage.IsControlMessage()) - { - next(); - return; - } + FlowDetailsForRequestingSagaToOutgoingMessage(context); - ActiveSagaInstance saga; + return next(context); + } - if (context.TryGet(out saga) && !saga.NotFound) - { - context.OutgoingLogicalMessage.Headers[Headers.OriginatingSagaId] = saga.SagaId; - context.OutgoingLogicalMessage.Headers[Headers.OriginatingSagaType] = saga.Metadata.SagaType.AssemblyQualifiedName; - } + static void FlowDetailsForRequestingSagaToOutgoingMessage(IOutgoingReplyContext context) + { + IncomingMessage incomingMessage; - //auto correlate with the saga we are replying to if needed - if (context.DeliveryOptions is ReplyOptions && context.IncomingMessage != null) + if (context.TryGetIncomingPhysicalMessage(out incomingMessage)) { string sagaId; + + incomingMessage.Headers.TryGetValue(Headers.OriginatingSagaId, out sagaId); + string sagaType; - if (context.IncomingMessage.Headers.TryGetValue(Headers.OriginatingSagaId, out sagaId)) + incomingMessage.Headers.TryGetValue(Headers.OriginatingSagaType, out sagaType); + + State state; + + if (context.Extensions.TryGet(out state)) + { + sagaId = state.SagaIdToUse; + sagaType = state.SagaTypeToUse; + } + + if (!string.IsNullOrEmpty(sagaId)) { - context.OutgoingLogicalMessage.Headers[Headers.SagaId] = sagaId; + context.Headers[Headers.SagaId] = sagaId; } - if (context.IncomingMessage.Headers.TryGetValue(Headers.OriginatingSagaType, out sagaType)) + if (!string.IsNullOrEmpty(sagaType)) { - context.OutgoingLogicalMessage.Headers[Headers.SagaType] = sagaType; + context.Headers[Headers.SagaType] = sagaType; } } + } + + public class State + { + public string SagaIdToUse { get; set; } - next(); + public string SagaTypeToUse { get; set; } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs b/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs index 9be49d222d4..2e0e312a9db 100644 --- a/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs +++ b/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs @@ -1,35 +1,65 @@ -namespace NServiceBus.Sagas +namespace NServiceBus { using System; - using NServiceBus.ObjectBuilder; - using NServiceBus.Unicast.Messages; - using Saga; - - /// - /// Finds the given type of saga by looking it up based on the given property. - /// - class PropertySagaFinder : SagaFinder where TSagaData : IContainSagaData - { - readonly ISagaPersister sagaPersister; + using System.Collections.Generic; + using System.Threading.Tasks; + using Extensibility; + using ObjectBuilder; + using Persistence; + using Sagas; + class PropertySagaFinder : SagaFinder where TSagaData : class, IContainSagaData + { public PropertySagaFinder(ISagaPersister sagaPersister) { this.sagaPersister = sagaPersister; } - internal override IContainSagaData Find(IBuilder builder,SagaFinderDefinition finderDefinition, LogicalMessage message) + public override async Task Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, object message) { - var propertyAccessor = (Func)finderDefinition.Properties["property-accessor"]; - var propertyValue = propertyAccessor(message.Instance); + var propertyAccessor = (Func) finderDefinition.Properties["property-accessor"]; + var propertyValue = propertyAccessor(message); - var sagaPropertyName = (string)finderDefinition.Properties["saga-property-name"]; + var sagaPropertyName = (string) finderDefinition.Properties["saga-property-name"]; if (sagaPropertyName.ToLower() == "id") { - return sagaPersister.Get((Guid)propertyValue); + return await sagaPersister.Get((Guid) propertyValue, storageSession, context).ConfigureAwait(false); } - return sagaPersister.Get(sagaPropertyName, propertyValue); + var lookupValues = context.GetOrCreate(); + + lookupValues.Add(sagaPropertyName, propertyValue); + + return await sagaPersister.Get(sagaPropertyName, propertyValue, storageSession, context).ConfigureAwait(false); + } + + ISagaPersister sagaPersister; + } + + + class SagaLookupValues + { + public void Add(string propertyName, object propertyValue) + { + entries[typeof(TSagaData)] = new LookupValue + { + PropertyName = propertyName, + PropertyValue = propertyValue + }; + } + + public bool TryGet(Type sagaType, out LookupValue value) + { + return entries.TryGetValue(sagaType, out value); + } + + Dictionary entries = new Dictionary(); + + public class LookupValue + { + public string PropertyName { get; set; } + public object PropertyValue { get; set; } } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/Saga.cs b/src/NServiceBus.Core/Sagas/Saga.cs new file mode 100644 index 00000000000..a4de0e47c6f --- /dev/null +++ b/src/NServiceBus.Core/Sagas/Saga.cs @@ -0,0 +1,150 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Extensibility; + + /// + /// This class is used to define sagas containing data and handling a message. + /// To handle more message types, implement + /// for the relevant types. + /// To signify that the receipt of a message should start this saga, + /// implement for the relevant message type. + /// + public abstract partial class Saga + { + /// + /// The saga's typed data. + /// + public IContainSagaData Entity { get; set; } + + /// + /// Indicates that the saga is complete. + /// In order to set this value, use the method. + /// + public bool Completed { get; private set; } + + /// + /// Override this method in order to configure how this saga's data should be found. + /// + internal protected abstract void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration); + + /// + /// Request for a timeout to occur at the given . + /// + /// The context which is used to send the timeout. + /// to send timeout . + protected Task RequestTimeout(IMessageHandlerContext context, DateTime at) where TTimeoutMessageType : new() + { + return RequestTimeout(context, at, new TTimeoutMessageType()); + } + + /// + /// Request for a timeout to occur at the given . + /// + /// The context which is used to send the timeout. + /// to send timeout . + /// The message to send after is reached. + protected Task RequestTimeout(IMessageHandlerContext context, DateTime at, TTimeoutMessageType timeoutMessage) + { + if (at.Kind == DateTimeKind.Unspecified) + { + throw new InvalidOperationException("Kind property of DateTime 'at' must be specified."); + } + + VerifySagaCanHandleTimeout(timeoutMessage); + + var options = new SendOptions(); + + options.DoNotDeliverBefore(at); + options.RouteToThisEndpoint(); + + SetTimeoutHeaders(options); + + return context.Send(timeoutMessage, options); + } + + /// + /// Request for a timeout to occur within the give . + /// + /// The context which is used to send the timeout. + /// Given to delay timeout message by. + protected Task RequestTimeout(IMessageHandlerContext context, TimeSpan within) where TTimeoutMessageType : new() + { + return RequestTimeout(context, within, new TTimeoutMessageType()); + } + + /// + /// Request for a timeout to occur within the given . + /// + /// The context which is used to send the timeout. + /// Given to delay timeout message by. + /// The message to send after expires. + protected Task RequestTimeout(IMessageHandlerContext context, TimeSpan within, TTimeoutMessageType timeoutMessage) + { + VerifySagaCanHandleTimeout(timeoutMessage); + + var sendOptions = new SendOptions(); + + sendOptions.DelayDeliveryWith(within); + sendOptions.RouteToThisEndpoint(); + + SetTimeoutHeaders(sendOptions); + + return context.Send(timeoutMessage, sendOptions); + } + + /// + /// Sends the using the bus to the endpoint that caused this saga to start. + /// + protected Task ReplyToOriginator(IMessageHandlerContext context, object message) + { + if (string.IsNullOrEmpty(Entity.Originator)) + { + throw new Exception("Entity.Originator cannot be null. Perhaps the sender is a SendOnly endpoint."); + } + + var options = new ReplyOptions(); + + options.SetDestination(Entity.Originator); + options.SetCorrelationId(Entity.OriginalMessageId); + + //until we have metadata we just set this to null to avoid our own saga id being set on outgoing messages since + //that would cause the saga that started us (if it was a saga) to not be found. When we have metadata available in the future we'll set the correct id and type + // and get true auto correlation to work between sagas + options.Context.Set(new PopulateAutoCorrelationHeadersForRepliesBehavior.State + { + SagaTypeToUse = null, + SagaIdToUse = null + }); + + return context.Reply(message, options); + } + + /// + /// Marks the saga as complete. + /// This may result in the sagas state being deleted by the persister. + /// + protected void MarkAsComplete() + { + Completed = true; + } + + void VerifySagaCanHandleTimeout(TTimeoutMessageType timeoutMessage) + { + var canHandleTimeoutMessage = this is IHandleTimeouts; + if (!canHandleTimeoutMessage) + { + var message = $"The type '{GetType().Name}' cannot request timeouts for '{timeoutMessage}' because it does not implement 'IHandleTimeouts<{typeof(TTimeoutMessageType).FullName}>'"; + throw new Exception(message); + } + } + + void SetTimeoutHeaders(ExtendableOptions options) + { + options.SetHeader(Headers.SagaId, Entity.Id.ToString()); + options.SetHeader(Headers.IsSagaTimeoutMessage, bool.TrueString); + options.SetHeader(Headers.SagaType, GetType().AssemblyQualifiedName); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaCorrelationProperty.cs b/src/NServiceBus.Core/Sagas/SagaCorrelationProperty.cs new file mode 100644 index 00000000000..c36af5fdc0c --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaCorrelationProperty.cs @@ -0,0 +1,35 @@ +namespace NServiceBus.Sagas +{ + /// + /// The property that this saga is correlated on. + /// + public class SagaCorrelationProperty + { + /// + /// Initializes the correlation property. + /// + public SagaCorrelationProperty(string name, object value) + { + Guard.AgainstNullAndEmpty(nameof(name), name); + Guard.AgainstNull(nameof(value), value); + + Name = name; + Value = value; + } + + /// + /// The name of the property. + /// + public string Name { get; private set; } + + /// + /// The property value. + /// + public object Value { get; private set; } + + /// + /// Represents a saga with no correlated property. + /// + public static SagaCorrelationProperty None { get; } = null; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaFinder.cs b/src/NServiceBus.Core/Sagas/SagaFinder.cs index 3089c22a48a..e438f81ef5f 100644 --- a/src/NServiceBus.Core/Sagas/SagaFinder.cs +++ b/src/NServiceBus.Core/Sagas/SagaFinder.cs @@ -1,12 +1,13 @@ namespace NServiceBus { - using NServiceBus.ObjectBuilder; - using NServiceBus.Saga; - using NServiceBus.Sagas; - using NServiceBus.Unicast.Messages; + using System.Threading.Tasks; + using Extensibility; + using ObjectBuilder; + using Persistence; + using Sagas; abstract class SagaFinder { - internal abstract IContainSagaData Find(IBuilder builder,SagaFinderDefinition finderDefinition, LogicalMessage message); + public abstract Task Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, object message); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaFinderDefinition.cs b/src/NServiceBus.Core/Sagas/SagaFinderDefinition.cs index 822dcace4c5..5d2e3fc3290 100644 --- a/src/NServiceBus.Core/Sagas/SagaFinderDefinition.cs +++ b/src/NServiceBus.Core/Sagas/SagaFinderDefinition.cs @@ -4,28 +4,42 @@ namespace NServiceBus.Sagas using System.Collections.Generic; /// - /// Defines a message finder + /// Defines a message finder. /// - class SagaFinderDefinition + public class SagaFinderDefinition { - internal SagaFinderDefinition() + /// + /// Initializes a new instance of the class. + /// + /// The type of the finder. + /// The type of message this finder is associated with. + /// Custom properties. + internal SagaFinderDefinition(Type type, Type messageType, Dictionary properties) { - Properties = new Dictionary(); + Type = type; + MessageType = messageType; + MessageTypeName = messageType.FullName; + Properties = properties; } /// - /// Custom properties + /// The type of the finder. + /// + public Type Type { get; private set; } + + /// + /// The type of message this finder is associated with. /// - public Dictionary Properties; - + public Type MessageType { get; private set; } + /// - /// The type of the finder + /// The full name of the message type. /// - public Type Type { get; set; } + public string MessageTypeName { get; private set; } /// - /// The type of message this finder is associated with + /// Custom properties. /// - public string MessageType { get; set; } + public Dictionary Properties { get; private set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaInvocationResult.cs b/src/NServiceBus.Core/Sagas/SagaInvocationResult.cs new file mode 100644 index 00000000000..91438fd21a7 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaInvocationResult.cs @@ -0,0 +1,29 @@ +namespace NServiceBus +{ + class SagaInvocationResult + { + public bool WasFound => state != State.SagaNotFound; + + public void SagaFound() + { + state = State.SagaFound; + } + + public void SagaNotFound() + { + if (state == State.Unknown) + { + state = State.SagaNotFound; + } + } + + State state; + + enum State + { + Unknown, + SagaFound, + SagaNotFound + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaLoader.cs b/src/NServiceBus.Core/Sagas/SagaLoader.cs new file mode 100644 index 00000000000..783a784a9d5 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaLoader.cs @@ -0,0 +1,12 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Extensibility; + using Persistence; + using Sagas; + + interface SagaLoader + { + Task Load(ISagaPersister persister, string sagaId, SynchronizedStorageSession storageSession, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaMessage.cs b/src/NServiceBus.Core/Sagas/SagaMessage.cs index 5fe3db77ed9..34e53f7574e 100644 --- a/src/NServiceBus.Core/Sagas/SagaMessage.cs +++ b/src/NServiceBus.Core/Sagas/SagaMessage.cs @@ -1,24 +1,37 @@ namespace NServiceBus.Sagas { + using System; + /// - /// Representation of a message that is related to a saga + /// Representation of a message that is related to a saga. /// - class SagaMessage + public class SagaMessage { /// - /// True if the message can start the saga - /// - public readonly bool IsAllowedToStartSaga; - - /// - /// The type of the message + /// Creates a new instance of . /// - public readonly string MessageType; - - internal SagaMessage(string messageType, bool isAllowedToStart) + /// Type of the message. + /// true if the message can start the saga, false otherwise. + internal SagaMessage(Type messageType, bool isAllowedToStart) { MessageType = messageType; + MessageTypeName = messageType.FullName; IsAllowedToStartSaga = isAllowedToStart; } + + /// + /// The type of the message. + /// + public Type MessageType { get; private set; } + + /// + /// The full name of the message type. + /// + public string MessageTypeName { get; private set; } + + /// + /// True if the message can start the saga. + /// + public bool IsAllowedToStartSaga { get; private set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaMetaModel.cs b/src/NServiceBus.Core/Sagas/SagaMetaModel.cs deleted file mode 100644 index 6d0420a4cc7..00000000000 --- a/src/NServiceBus.Core/Sagas/SagaMetaModel.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus.Sagas -{ - using System.Collections; - using System.Collections.Generic; - using System.Linq; - - class SagaMetaModel : IEnumerable - { - public SagaMetaModel(IEnumerable foundSagas) - { - var sagas = foundSagas.ToList(); - - foreach (var saga in sagas) - { - byEntityName[saga.EntityName] = saga; - } - - foreach (var saga in sagas) - { - byName[saga.Name] = saga; - } - } - - public SagaMetadata FindByEntityName(string name) - { - return byEntityName[name]; - } - - public IEnumerator GetEnumerator() - { - return byEntityName.Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public SagaMetadata FindByName(string name) - { - return byName[name]; - } - - Dictionary byEntityName = new Dictionary(); - Dictionary byName = new Dictionary(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaMetadata.cs b/src/NServiceBus.Core/Sagas/SagaMetadata.cs index 8356a623661..c2fac0d67e8 100644 --- a/src/NServiceBus.Core/Sagas/SagaMetadata.cs +++ b/src/NServiceBus.Core/Sagas/SagaMetadata.cs @@ -2,54 +2,115 @@ namespace NServiceBus.Sagas { using System; using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Runtime.Serialization; /// - /// Contains metadata for known sagas + /// Contains metadata for known sagas. /// - class SagaMetadata + public class SagaMetadata { - public SagaMetadata(IEnumerable messages, IEnumerable finders) + /// + /// Initializes a new instance of the class. + /// + /// The name of the saga. + /// The type for this saga. + /// The name of the saga data entity. + /// The type of the related saga entity. + /// The property this saga is correlated on if any. + /// The messages collection that a saga handles. + /// The finder definition collection that can find this saga. + public SagaMetadata(string name, Type sagaType, string entityName, Type sagaEntityType, CorrelationPropertyMetadata correlationProperty, IReadOnlyCollection messages, IReadOnlyCollection finders) { - CorrelationProperties = new List(); + this.correlationProperty = correlationProperty; + Name = name; + EntityName = entityName; + SagaEntityType = sagaEntityType; + SagaType = sagaType; + + + if (!messages.Any(m => m.IsAllowedToStartSaga)) + { + throw new Exception($@" +Sagas must have at least one message that is allowed to start the saga. Add at least one `IAmStartedByMessages` to the {name} saga."); + } + + if (correlationProperty != null) + { + if (!AllowedCorrelationPropertyTypes.Contains(correlationProperty.Type)) + { + var supportedTypes = string.Join(",", AllowedCorrelationPropertyTypes.Select(t => t.Name)); + + throw new Exception($@" +{correlationProperty.Type.Name} is not supported for correlated properties. Change the correlation property {correlationProperty.Name} on saga {name} to any of the supported types, {supportedTypes}, or use a custom saga finder."); + } + } associatedMessages = new Dictionary(); foreach (var sagaMessage in messages) { - associatedMessages[sagaMessage.MessageType] = sagaMessage; + associatedMessages[sagaMessage.MessageTypeName] = sagaMessage; } - sagaFinders = new Dictionary(); foreach (var finder in finders) { - sagaFinders[finder.MessageType] = finder; + sagaFinders[finder.MessageTypeName] = finder; } - } + /// + /// Returns the list of messages that is associated with this saga. + /// + public IReadOnlyCollection AssociatedMessages => associatedMessages.Values.ToList(); + + /// + /// Gets the list of finders for this saga. + /// + public IReadOnlyCollection Finders => sagaFinders.Values.ToList(); /// - /// Properties this saga is correlated on + /// The name of the saga. /// - public List CorrelationProperties; + public string Name { get; private set; } /// - /// The name of the saga + /// The name of the saga data entity. /// - public string Name; + public string EntityName { get; private set; } /// - /// The name of the saga data entity + /// The type of the related saga entity. /// - public string EntityName; + public Type SagaEntityType { get; private set; } /// - /// True if the specified message type is allowed to start the saga + /// The type for this saga. + /// + public Type SagaType { get; private set; } + + /// + /// Property this saga is correlated on. + /// + public bool TryGetCorrelationProperty(out CorrelationPropertyMetadata property) + { + property = correlationProperty; + + return property != null; + } + + internal static bool IsSagaType(Type t) + { + return typeof(Saga).IsAssignableFrom(t) && t != typeof(Saga) && !t.IsGenericType && !t.IsAbstract; + } + + /// + /// True if the specified message type is allowed to start the saga. /// - /// - /// public bool IsMessageAllowedToStartTheSaga(string messageType) { SagaMessage sagaMessage; @@ -62,44 +123,362 @@ public bool IsMessageAllowedToStartTheSaga(string messageType) } /// - /// Returns the list of messages that is associated with this saga + /// Gets the configured finder for this message. /// - public IEnumerable AssociatedMessages + /// The message . + /// The finder if present. + /// True if finder exists. + public bool TryGetFinder(string messageType, out SagaFinderDefinition finderDefinition) { - get { return associatedMessages.Values; } + return sagaFinders.TryGetValue(messageType, out finderDefinition); } /// - /// Gets the list of finders for this saga + /// Creates a from a specific Saga type. /// - public IEnumerable Finders + /// A type representing a Saga. Must be a non-generic type inheriting from . + /// An instance of describing the Saga. + public static SagaMetadata Create(Type sagaType) { - get { return sagaFinders.Values; } + return Create(sagaType, new List(), new Conventions()); } /// - /// The type of the related saga entity + /// Creates a from a specific Saga type. /// - public Type SagaEntityType; + /// A type representing a Saga. Must be a non-generic type inheriting from . + /// Additional available types, used to locate saga finders and other related classes. + /// Custom conventions to use while scanning types. + /// An instance of describing the Saga. + public static SagaMetadata Create(Type sagaType, IEnumerable availableTypes, Conventions conventions) + { + if (!IsSagaType(sagaType)) + { + throw new Exception(sagaType.FullName + " is not a saga"); + } - /// - /// The type for this saga - /// - public Type SagaType; + var genericArguments = GetBaseSagaType(sagaType).GetGenericArguments(); + if (genericArguments.Length != 1) + { + throw new Exception($"'{sagaType.Name}' saga type does not implement Saga"); + } + var saga = (Saga) FormatterServices.GetUninitializedObject(sagaType); + var mapper = new SagaMapper(); + saga.ConfigureHowToFindSaga(mapper); - /// - /// Gets the configured finder for this message - /// - /// - /// The finder if present - /// True if finder exists - public bool TryGetFinder(string messageType,out SagaFinderDefinition finderDefinition) + var sagaEntityType = genericArguments.Single(); + + ApplyScannedFinders(mapper, sagaEntityType, availableTypes, conventions); + + var finders = new List(); + + + var propertyMappings = mapper.Mappings.Where(m => !m.HasCustomFinderMap) + .GroupBy(m => m.SagaPropName) + .ToList(); + + if (propertyMappings.Count > 1) + { + var messageTypes = string.Join(",", propertyMappings.SelectMany(g => g.Select(m => m.MessageType.FullName)).Distinct()); + throw new Exception($"Sagas can only have mappings that correlate on a single saga property. Use custom finders to correlate {messageTypes} to saga {sagaType.Name}"); + } + + CorrelationPropertyMetadata correlationProperty = null; + + if (propertyMappings.Any()) + { + var mapping = propertyMappings.Single().First(); + correlationProperty = new CorrelationPropertyMetadata(mapping.SagaPropName, mapping.SagaPropType); + } + + var associatedMessages = GetAssociatedMessages(sagaType) + .ToList(); + + foreach (var mapping in mapper.Mappings) + { + var associatedMessage = associatedMessages.FirstOrDefault(m => mapping.MessageType.IsAssignableFrom(m.MessageType)); + if (associatedMessage == null) + { + var msgType = mapping.MessageType.Name; + if (mapping.HasCustomFinderMap) + { + throw new Exception($"Custom saga finder {mapping.CustomFinderType.FullName} maps message type {msgType} for saga {sagaType.Name}, but the saga does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>."); + } + throw new Exception($"Saga {sagaType.Name} contains a mapping for {msgType} in the ConfigureHowToFindSaga method, but does not handle that message. If {sagaType.Name} is supposed to handle this message, it should implement IAmStartedByMessages<{msgType}> or IHandleMessages<{msgType}>."); + } + SetFinderForMessage(mapping, sagaEntityType, finders); + } + + foreach (var messageType in associatedMessages) + { + if (messageType.IsAllowedToStartSaga) + { + var match = mapper.Mappings.FirstOrDefault(m => m.MessageType.IsAssignableFrom(messageType.MessageType)); + if (match == null) + { + var simpleName = messageType.MessageType.Name; + throw new Exception($"Message type {simpleName} can start the saga {sagaType.Name} (the saga implements IAmStartedByMessages<{simpleName}>) but does not map that message to saga data. In the ConfigureHowToFindSaga method, add a mapping using:{Environment.NewLine} mapper.ConfigureMapping<{simpleName}>(message => message.SomeMessageProperty).ToSaga(saga => saga.MatchingSagaProperty);"); + } + } + } + + return new SagaMetadata(sagaType.FullName, sagaType, sagaEntityType.FullName, sagaEntityType, correlationProperty, associatedMessages, finders); + } + + static void ApplyScannedFinders(SagaMapper mapper, Type sagaEntityType, IEnumerable availableTypes, Conventions conventions) + { + var actualFinders = availableTypes.Where(t => typeof(IFinder).IsAssignableFrom(t) && t.IsClass) + .ToList(); + + foreach (var finderType in actualFinders) + { + foreach (var interfaceType in finderType.GetInterfaces()) + { + var args = interfaceType.GetGenericArguments(); + //since we don't want to process the IFinder type + if (args.Length != 2) + { + continue; + } + + var entityType = args[0]; + if (entityType != sagaEntityType) + { + continue; + } + + var messageType = args[1]; + if (!conventions.IsMessageType(messageType)) + { + var error = $"A custom IFindSagas must target a valid message type as defined by the message conventions. Change '{messageType.FullName}' to a valid message type or add it to the message conventions. Finder name '{finderType.FullName}'."; + throw new Exception(error); + } + + var existingMapping = mapper.Mappings.SingleOrDefault(m => m.MessageType == messageType); + if (existingMapping != null) + { + var bothMappingAndFinder = $"A custom IFindSagas and an existing mapping where found for message '{messageType.FullName}'. Either remove the message mapping for remove the finder. Finder name '{finderType.FullName}'."; + throw new Exception(bothMappingAndFinder); + } + mapper.ConfigureCustomFinder(finderType, messageType); + } + } + } + + static void SetFinderForMessage(SagaToMessageMap mapping, Type sagaEntityType, List finders) { - return sagaFinders.TryGetValue(messageType,out finderDefinition); + if (mapping.HasCustomFinderMap) + { + finders.Add(new SagaFinderDefinition(typeof(CustomFinderAdapter<,>).MakeGenericType(sagaEntityType, mapping.MessageType), mapping.MessageType, new Dictionary + { + {"custom-finder-clr-type", mapping.CustomFinderType} + })); + } + else + { + finders.Add(new SagaFinderDefinition(typeof(PropertySagaFinder<>).MakeGenericType(sagaEntityType), mapping.MessageType, new Dictionary + { + {"property-accessor", mapping.MessageProp}, + {"saga-property-name", mapping.SagaPropName} + })); + } + } + + static IEnumerable GetAssociatedMessages(Type sagaType) + { + var result = GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IAmStartedByMessages<>)) + .Select(t => new SagaMessage(t, true)).ToList(); + + foreach (var messageType in GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IHandleMessages<>))) + { + if (result.Any(m => m.MessageType == messageType)) + { + continue; + } + result.Add(new SagaMessage(messageType, false)); + } + + foreach (var messageType in GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IHandleTimeouts<>))) + { + if (result.Any(m => m.MessageType == messageType)) + { + continue; + } + result.Add(new SagaMessage(messageType, false)); + } + + return result; + } + + static IEnumerable GetMessagesCorrespondingToFilterOnSaga(Type sagaType, Type filter) + { + foreach (var interfaceType in sagaType.GetInterfaces()) + { + foreach (var argument in interfaceType.GetGenericArguments()) + { + var genericType = filter.MakeGenericType(argument); + var isOfFilterType = genericType == interfaceType; + if (!isOfFilterType) + { + continue; + } + yield return argument; + } + } + } + + static Type GetBaseSagaType(Type t) + { + var currentType = t.BaseType; + var previousType = t; + + while (currentType != null) + { + if (currentType == typeof(Saga)) + { + return previousType; + } + + previousType = currentType; + currentType = currentType.BaseType; + } + + throw new InvalidOperationException(); } Dictionary associatedMessages; + CorrelationPropertyMetadata correlationProperty; Dictionary sagaFinders; + + static Type[] AllowedCorrelationPropertyTypes = + { + typeof(Guid), + typeof(string), + typeof(long), + typeof(ulong), + typeof(int), + typeof(uint), + typeof(short), + typeof(ushort) + }; + + class SagaMapper : IConfigureHowToFindSagaWithMessage + { + void IConfigureHowToFindSagaWithMessage.ConfigureMapping(Expression> sagaEntityProperty, Expression> messageExpression) + { + var sagaMember = Reflect.GetMemberInfo(sagaEntityProperty, true); + var sagaProp = sagaMember as PropertyInfo; + if (sagaProp == null) + { + throw new InvalidOperationException($"Mapping expressions for saga members must point to properties. Change member {sagaMember.Name} on {typeof(TSagaEntity).Name} to a property."); + } + + ValidateMapping(messageExpression, sagaProp); + + ThrowIfNotPropertyLambdaExpression(sagaEntityProperty, sagaProp); + var compiledMessageExpression = messageExpression.Compile(); + var messageFunc = new Func(o => compiledMessageExpression((TMessage) o)); + + Mappings.Add(new SagaToMessageMap + { + MessageProp = messageFunc, + SagaPropName = sagaProp.Name, + SagaPropType = sagaProp.PropertyType, + MessageType = typeof(TMessage) + }); + } + + static void ValidateMapping(Expression> messageExpression, PropertyInfo sagaProp) + { + var memberExpr = messageExpression.Body as MemberExpression; + + if (messageExpression.Body.NodeType == ExpressionType.Convert) + { + memberExpr = ((UnaryExpression) messageExpression.Body).Operand as MemberExpression; + } + + if (memberExpr == null) + { + return; + } + + var propertyInfo = memberExpr.Member as PropertyInfo; + + const string message = "When mapping a message to a saga, the member type on the message and the saga property must match. {0}.{1} is of type {2} and {3}.{4} is of type {5}."; + + if (propertyInfo != null) + { + if (propertyInfo.PropertyType != sagaProp.PropertyType) + { + throw new InvalidOperationException(string.Format(message, + propertyInfo.DeclaringType.Name, propertyInfo.Name, propertyInfo.PropertyType, + sagaProp.DeclaringType.Name, sagaProp.Name, sagaProp.PropertyType)); + } + + return; + } + + var fieldInfo = memberExpr.Member as FieldInfo; + + if (fieldInfo != null) + { + if (fieldInfo.FieldType != sagaProp.PropertyType) + { + throw new InvalidOperationException(string.Format(message, + fieldInfo.DeclaringType.Name, fieldInfo.Name, fieldInfo.FieldType, + sagaProp.DeclaringType.Name, sagaProp.Name, sagaProp.PropertyType)); + } + } + } + + public void ConfigureCustomFinder(Type finderType, Type messageType) + { + Mappings.Add(new SagaToMessageMap + { + MessageType = messageType, + CustomFinderType = finderType + }); + } + + // ReSharper disable once UnusedParameter.Local + void ThrowIfNotPropertyLambdaExpression(Expression> expression, PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentException( + $@"Only public properties are supported for mapping Sagas. The lambda expression provided '{expression.Body}' is not mapping to a Property."); + } + } + + public List Mappings = new List(); + } + + /// + /// Details about a saga data property used to correlate messages hitting the saga. + /// + public class CorrelationPropertyMetadata + { + /// + /// Creates a new instance of . + /// + /// The name of the correlation property. + /// The type of the correlation property. + public CorrelationPropertyMetadata(string name, Type type) + { + Name = name; + Type = type; + } + + /// + /// The name of the correlation property. + /// + public string Name { get; } + + /// + /// The type of the correlation property. + /// + public Type Type { get; } + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaMetadataCollection.cs b/src/NServiceBus.Core/Sagas/SagaMetadataCollection.cs new file mode 100644 index 00000000000..e1ff1fad994 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaMetadataCollection.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.Sagas +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Sagas metamodel. + /// + public class SagaMetadataCollection : IEnumerable + { + /// + /// Returns an enumerator that iterates through the collection. + /// + public IEnumerator GetEnumerator() + { + return byEntity.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Populates the model with saga metadata from the provided collection of types. + /// + /// A collection of types to scan for sagas. + public void Initialize(IEnumerable availableTypes) + { + Initialize(availableTypes, new Conventions()); + } + + /// + /// Populates the model with saga metadata from the provided collection of types. + /// + /// A collection of types to scan for sagas. + /// Custom conventions to be used while scanning types. + public void Initialize(IEnumerable availableTypes, Conventions conventions) + { + var availableTypesList = availableTypes.ToList(); + + var foundSagas = availableTypesList.Where(SagaMetadata.IsSagaType) + .Select(t => SagaMetadata.Create(t, availableTypesList, conventions)) + .ToList(); + + foreach (var saga in foundSagas) + { + byEntity[saga.SagaEntityType] = saga; + byType[saga.SagaType] = saga; + } + } + + /// + /// Returns a for an entity by entity name. + /// + /// Type of the entity (saga data). + /// An instance of . + public SagaMetadata FindByEntity(Type entityType) + { + return byEntity[entityType]; + } + + /// + /// Returns a for an entity by name. + /// + /// Saga type. + /// An instance of . + public SagaMetadata Find(Type sagaType) + { + return byType[sagaType]; + } + + Dictionary byEntity = new Dictionary(); + Dictionary byType = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs b/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs index db1f41574a7..280eee2fa50 100644 --- a/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs +++ b/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs @@ -1,134 +1,155 @@ namespace NServiceBus { using System; + using System.Collections.Generic; + using System.ComponentModel; using System.Linq; - using NServiceBus.Logging; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NServiceBus.Saga; - using NServiceBus.Sagas; - using NServiceBus.Timeout; - using NServiceBus.Transports; - using NServiceBus.Unicast; - using NServiceBus.Unicast.Messages; - - class SagaPersistenceBehavior : IBehavior - { - public ISagaPersister SagaPersister { get; set; } - - public IDeferMessages MessageDeferrer { get; set; } - - public IMessageHandlerRegistry MessageHandlerRegistry { get; set; } + using System.Threading.Tasks; + using Logging; + using Pipeline; + using Sagas; + using Transport; - public SagaMetaModel SagaMetaModel { get; set; } + class SagaPersistenceBehavior : IBehavior + { + public SagaPersistenceBehavior(ISagaPersister persister, ICancelDeferredMessages timeoutCancellation, SagaMetadataCollection sagaMetadataCollection) + { + sagaPersister = persister; + this.timeoutCancellation = timeoutCancellation; + this.sagaMetadataCollection = sagaMetadataCollection; + } - public void Invoke(IncomingContext context, Action next) + public async Task Invoke(IInvokeHandlerContext context, Func next) { currentContext = context; - // We need this for backwards compatibility because in v4.0.0 we still have this headers being sent as part of the message even if MessageIntent == MessageIntentEnum.Publish - if (context.PhysicalMessage.MessageIntent == MessageIntentEnum.Publish) - { - context.PhysicalMessage.Headers.Remove(Headers.SagaId); - context.PhysicalMessage.Headers.Remove(Headers.SagaType); - } + RemoveSagaHeadersIfProcessingAEvent(context); - var saga = context.MessageHandler.Instance as Saga.Saga; + var saga = context.MessageHandler.Instance as Saga; if (saga == null) { - next(); + await next(context).ConfigureAwait(false); return; } - var sagaMetadata = SagaMetaModel.FindByName(context.MessageHandler.Instance.GetType().FullName); - var sagaInstanceState = new ActiveSagaInstance(saga, sagaMetadata); + var sagaMetadata = sagaMetadataCollection.Find(context.MessageHandler.Instance.GetType()); + var sagaInstanceState = new ActiveSagaInstance(saga, sagaMetadata, () => DateTime.UtcNow); //so that other behaviors can access the saga - context.Set(sagaInstanceState); + context.Extensions.Set(sagaInstanceState); - var loadedEntity = TryLoadSagaEntity(sagaMetadata, context.IncomingLogicalMessage); + var loadedEntity = await TryLoadSagaEntity(sagaMetadata, context).ConfigureAwait(false); if (loadedEntity == null) { //if this message are not allowed to start the saga - if (IsMessageAllowedToStartTheSaga(context.IncomingLogicalMessage, sagaMetadata)) + if (IsMessageAllowedToStartTheSaga(context, sagaMetadata)) { - context.Set("Sagas.SagaWasInvoked", true); - - sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaMetadata, context.IncomingLogicalMessage)); + context.Extensions.Get().SagaFound(); + sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaMetadata, context)); } else { + if (!context.Headers.ContainsKey(Headers.SagaId)) + { + var finderDefinition = GetSagaFinder(sagaMetadata, context); + if (finderDefinition == null) + { + throw new Exception($"Message type {context.MessageBeingHandled.GetType().Name} is handled by saga {sagaMetadata.SagaType.Name}, but the saga does not contain a property mapping or custom saga finder to map the message to saga data. Consider adding a mapping in the saga's {nameof(Saga.ConfigureHowToFindSaga)} method."); + } + } + sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts - if (IsTimeoutMessage(context.IncomingLogicalMessage)) + if (IsTimeoutMessage(context.Headers)) { - context.Set("Sagas.SagaWasInvoked", true); - - logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.PhysicalMessage.Id); + context.Extensions.Get().SagaFound(); + logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.MessageId); } else { - context.Set("Sagas.InvokeSagaNotFound", true); + context.Extensions.Get().SagaNotFound(); } } } else { - context.Set("Sagas.SagaWasInvoked", true); - + context.Extensions.Get().SagaFound(); sagaInstanceState.AttachExistingEntity(loadedEntity); } - if (IsTimeoutMessage(context.IncomingLogicalMessage)) - { - context.MessageHandler.Invocation = MessageHandlerRegistry.InvokeTimeout; - } - - next(); + await next(context).ConfigureAwait(false); if (sagaInstanceState.NotFound) { return; } - sagaInstanceState.ValidateIdHasNotChanged(); - LogSaga(sagaInstanceState, context); if (saga.Completed) { if (!sagaInstanceState.IsNew) { - SagaPersister.Complete(saga.Entity); + await sagaPersister.Complete(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } if (saga.Entity.Id != Guid.Empty) { - NotifyTimeoutManagerThatSagaHasCompleted(saga); + await timeoutCancellation.CancelDeferredMessages(saga.Entity.Id.ToString(), context).ConfigureAwait(false); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); + + sagaInstanceState.Completed(); } else { + sagaInstanceState.ValidateChanges(); + if (sagaInstanceState.IsNew) { - SagaPersister.Save(saga.Entity); + ActiveSagaInstance.CorrelationPropertyInfo correlationProperty; + var sagaCorrelationProperty = SagaCorrelationProperty.None; + + if (sagaInstanceState.TryGetCorrelationProperty(out correlationProperty)) + { + sagaCorrelationProperty = new SagaCorrelationProperty(correlationProperty.PropertyInfo.Name, correlationProperty.Value); + } + + await sagaPersister.Save(saga.Entity, sagaCorrelationProperty, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } else { - SagaPersister.Update(saga.Entity); + await sagaPersister.Update(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } + + sagaInstanceState.Updated(); } } - static bool IsMessageAllowedToStartTheSaga(LogicalMessage message, SagaMetadata sagaMetadata) + static void RemoveSagaHeadersIfProcessingAEvent(IInvokeHandlerContext context) + { + // We need this for backwards compatibility because in v4.0.0 we still have this headers being sent as part of the message even if MessageIntent == MessageIntentEnum.Publish + string messageIntentString; + if (context.Headers.TryGetValue(Headers.MessageIntent, out messageIntentString)) + { + MessageIntentEnum messageIntent; + + if (Enum.TryParse(messageIntentString, true, out messageIntent) && messageIntent == MessageIntentEnum.Publish) + { + context.Headers.Remove(Headers.SagaId); + context.Headers.Remove(Headers.SagaType); + } + } + } + + static bool IsMessageAllowedToStartTheSaga(IInvokeHandlerContext context, SagaMetadata sagaMetadata) { string sagaType; - if (message.Headers.ContainsKey(Headers.SagaId) && - message.Headers.TryGetValue(Headers.SagaType, out sagaType)) + if (context.Headers.ContainsKey(Headers.SagaId) && + context.Headers.TryGetValue(Headers.SagaType, out sagaType)) { //we want to move away from the assembly fully qualified name since that will break if you move sagas // between assemblies. We use the fullname instead which is enough to identify the saga @@ -139,39 +160,21 @@ static bool IsMessageAllowedToStartTheSaga(LogicalMessage message, SagaMetadata } } - return message.Metadata.MessageHierarchy.Any(messageType => sagaMetadata.IsMessageAllowedToStartTheSaga(messageType.FullName)); - } - - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.1", Message = "Enriching the headers for saga related information has been moved to the SagaAudit plugin in ServiceControl. Add a reference to the Saga audit plugin in your endpoint to get more information.")] - void LogSaga(ActiveSagaInstance saga, IncomingContext context) - { - - var audit = string.Format("{0}:{1}", saga.Metadata.Name, saga.SagaId); - - string header; - - if (context.IncomingLogicalMessage.Headers.TryGetValue(Headers.InvokedSagas, out header)) - { - context.IncomingLogicalMessage.Headers[Headers.InvokedSagas] += string.Format(";{0}", audit); - } - else - { - context.IncomingLogicalMessage.Headers[Headers.InvokedSagas] = audit; - } + return context.MessageMetadata.MessageHierarchy.Any(messageType => sagaMetadata.IsMessageAllowedToStartTheSaga(messageType.FullName)); } - static bool IsTimeoutMessage(LogicalMessage message) + static bool IsTimeoutMessage(Dictionary headers) { string isSagaTimeout; - if (message.Headers.TryGetValue(Headers.IsSagaTimeoutMessage, out isSagaTimeout)) + if (headers.TryGetValue(Headers.IsSagaTimeoutMessage, out isSagaTimeout)) { return true; } string version; - if (!message.Headers.TryGetValue(Headers.NServiceBusVersion, out version)) + if (!headers.TryGetValue(Headers.NServiceBusVersion, out version)) { return false; } @@ -182,7 +185,7 @@ static bool IsTimeoutMessage(LogicalMessage message) } string sagaId; - if (message.Headers.TryGetValue(Headers.SagaId, out sagaId)) + if (headers.TryGetValue(Headers.SagaId, out sagaId)) { if (string.IsNullOrEmpty(sagaId)) { @@ -195,7 +198,7 @@ static bool IsTimeoutMessage(LogicalMessage message) } string expire; - if (message.Headers.TryGetValue(TimeoutManagerHeaders.Expire, out expire)) + if (headers.TryGetValue(TimeoutManagerHeaders.Expire, out expire)) { if (string.IsNullOrEmpty(expire)) { @@ -207,85 +210,92 @@ static bool IsTimeoutMessage(LogicalMessage message) return false; } - message.Headers[Headers.IsSagaTimeoutMessage] = Boolean.TrueString; + headers[Headers.IsSagaTimeoutMessage] = bool.TrueString; return true; } - IContainSagaData TryLoadSagaEntity(SagaMetadata metadata, LogicalMessage message) - { + Task TryLoadSagaEntity(SagaMetadata metadata, IInvokeHandlerContext context) + { string sagaId; - if (message.Headers.TryGetValue(Headers.SagaId, out sagaId) && !string.IsNullOrEmpty(sagaId)) + if (context.Headers.TryGetValue(Headers.SagaId, out sagaId) && !string.IsNullOrEmpty(sagaId)) { var sagaEntityType = metadata.SagaEntityType; //since we have a saga id available we can now shortcut the finders and just load the saga var loaderType = typeof(LoadSagaByIdWrapper<>).MakeGenericType(sagaEntityType); - var loader = (SagaLoader)Activator.CreateInstance(loaderType); + var loader = (SagaLoader) Activator.CreateInstance(loaderType); - return loader.Load(SagaPersister, sagaId); + return loader.Load(sagaPersister, sagaId, context.SynchronizedStorageSession, context.Extensions); } - SagaFinderDefinition finderDefinition = null; - - foreach (var messageType in message.Metadata.MessageHierarchy) - { - if (metadata.TryGetFinder(messageType.FullName, out finderDefinition)) - { - break; - } - } + var finderDefinition = GetSagaFinder(metadata, context); //check if we could find a finder if (finderDefinition == null) { - return null; + return DefaultSagaDataCompletedTask; } var finderType = finderDefinition.Type; + var finder = (SagaFinder) currentContext.Builder.Build(finderType); - var finder = currentContext.Builder.Build(finderType); - - return ((SagaFinder)finder).Find(currentContext.Builder, finderDefinition, message); + return finder.Find(currentContext.Builder, finderDefinition, context.SynchronizedStorageSession, context.Extensions, context.MessageBeingHandled); } - void NotifyTimeoutManagerThatSagaHasCompleted(Saga.Saga saga) + SagaFinderDefinition GetSagaFinder(SagaMetadata metadata, IInvokeHandlerContext context) { - MessageDeferrer.ClearDeferredMessages(Headers.SagaId, saga.Entity.Id.ToString()); + foreach (var messageType in context.MessageMetadata.MessageHierarchy) + { + SagaFinderDefinition finderDefinition; + if (metadata.TryGetFinder(messageType.FullName, out finderDefinition)) + { + return finderDefinition; + } + } + return null; } - IContainSagaData CreateNewSagaEntity(SagaMetadata metadata,LogicalMessage message) + IContainSagaData CreateNewSagaEntity(SagaMetadata metadata, IInvokeHandlerContext context) { var sagaEntityType = metadata.SagaEntityType; - var sagaEntity = (IContainSagaData)Activator.CreateInstance(sagaEntityType); + var sagaEntity = (IContainSagaData) Activator.CreateInstance(sagaEntityType); sagaEntity.Id = CombGuid.Generate(); - sagaEntity.OriginalMessageId = message.Headers[Headers.MessageId]; + sagaEntity.OriginalMessageId = context.MessageId; string replyToAddress; - if (message.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress)) + if (context.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress)) { sagaEntity.Originator = replyToAddress; } - return sagaEntity; - } + var lookupValues = context.Extensions.GetOrCreate(); - IncomingContext currentContext; + SagaLookupValues.LookupValue value; + if (lookupValues.TryGet(sagaEntityType, out value)) + { + var propertyInfo = sagaEntityType.GetProperty(value.PropertyName); - static ILog logger = LogManager.GetLogger(); + var convertedValue = TypeDescriptor.GetConverter(propertyInfo.PropertyType) + .ConvertFromInvariantString(value.PropertyValue.ToString()); - public class Registration : RegisterStep - { - public Registration() - : base(WellKnownStep.InvokeSaga, typeof(SagaPersistenceBehavior), "Invokes the saga logic") - { - InsertBefore(WellKnownStep.InvokeHandlers); - InsertAfter("SetCurrentMessageBeingHandled"); + propertyInfo.SetValue(sagaEntity, convertedValue); } + + return sagaEntity; } + + IInvokeHandlerContext currentContext; + SagaMetadataCollection sagaMetadataCollection; + + ISagaPersister sagaPersister; + ICancelDeferredMessages timeoutCancellation; + + static Task DefaultSagaDataCompletedTask = Task.FromResult(default(IContainSagaData)); + static ILog logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaPropertyMapper.cs b/src/NServiceBus.Core/Sagas/SagaPropertyMapper.cs new file mode 100644 index 00000000000..64e5411202b --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaPropertyMapper.cs @@ -0,0 +1,35 @@ +namespace NServiceBus +{ + using System; + using System.Linq.Expressions; + + /// + /// A helper class that proved syntactical sugar as part of . + /// + /// A type that implements . + public class SagaPropertyMapper where TSagaData : IContainSagaData + { + internal SagaPropertyMapper(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) + { + this.sagaMessageFindingConfiguration = sagaMessageFindingConfiguration; + } + + /// + /// Specify how to map between and . + /// + /// The message type to map to. + /// An that represents the message. + /// + /// A that provides the fluent chained + /// to link with + /// . + /// + public ToSagaExpression ConfigureMapping(Expression> messageProperty) + { + Guard.AgainstNull(nameof(messageProperty), messageProperty); + return new ToSagaExpression(sagaMessageFindingConfiguration, messageProperty); + } + + IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaT.cs b/src/NServiceBus.Core/Sagas/SagaT.cs new file mode 100644 index 00000000000..caff4e10e6a --- /dev/null +++ b/src/NServiceBus.Core/Sagas/SagaT.cs @@ -0,0 +1,50 @@ +namespace NServiceBus +{ + /// + /// This class is used to define sagas containing data and handling a message. + /// To handle more message types, implement + /// for the relevant types. + /// To signify that the receipt of a message should start this saga, + /// implement for the relevant message type. + /// + /// A type that implements . + public abstract class Saga : Saga where TSagaData : IContainSagaData, new() + { + /// + /// The saga's strongly typed data. Wraps . + /// + public TSagaData Data + { + get { return (TSagaData) Entity; } + set + { + Guard.AgainstNull(nameof(value), value); + Entity = value; + } + } + + + /// + /// Override this method in order to configure how this saga's data should be found. + /// + /// + /// Override and forwards it to the generic version + /// . + /// + internal protected override void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration) + { + ConfigureHowToFindSaga(new SagaPropertyMapper(sagaMessageFindingConfiguration)); + } + + /// + /// A generic version of wraps + /// in a generic helper class ( + /// ) to provide mappings specific to . + /// + /// + /// The that wraps the + /// . + /// + protected abstract void ConfigureHowToFindSaga(SagaPropertyMapper mapper); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaToMessageMap.cs b/src/NServiceBus.Core/Sagas/SagaToMessageMap.cs index d58f8bc06f3..47faf06d64d 100644 --- a/src/NServiceBus.Core/Sagas/SagaToMessageMap.cs +++ b/src/NServiceBus.Core/Sagas/SagaToMessageMap.cs @@ -1,12 +1,14 @@ -namespace NServiceBus.Sagas +namespace NServiceBus { using System; class SagaToMessageMap { + public bool HasCustomFinderMap => CustomFinderType != null; + public Type CustomFinderType; public Func MessageProp; - public string SagaPropName; public Type MessageType; - public Type CustomFinderType; + public string SagaPropName; + public Type SagaPropType; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/Sagas.cs b/src/NServiceBus.Core/Sagas/Sagas.cs index 6f5813e2486..4dad8a361b7 100644 --- a/src/NServiceBus.Core/Sagas/Sagas.cs +++ b/src/NServiceBus.Core/Sagas/Sagas.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; - using NServiceBus.ObjectBuilder; - using NServiceBus.Saga; using NServiceBus.Sagas; + using ObjectBuilder; + using Persistence; + using Transport; /// - /// Used to configure saga. + /// Used to configure saga. /// public class Sagas : Feature { @@ -27,26 +28,26 @@ internal Sagas() } }); - Prerequisite(config => config.Settings.GetAvailableTypes().Any(IsSagaType), "No sagas was found in scanned types"); + Defaults(s => s.Set(new SagaMetadataCollection())); + + Prerequisite(config => config.Settings.GetAvailableTypes().Any(IsSagaType), "No sagas were found in the scanned types"); } /// - /// See + /// See . /// protected internal override void Setup(FeatureConfigurationContext context) { - // Register the Saga related behaviors for incoming messages - context.Pipeline.Register(); - context.Pipeline.Register(); - - var typeBasedSagas = TypeBasedSagaMetaModel.Create(context.Settings.GetAvailableTypes(),conventions); + if (!PersistenceStartup.HasSupportFor(context.Settings)) + { + throw new Exception("The selected persistence doesn't have support for saga storage. Select another persistence or disable the sagas feature using endpointConfiguration.DisableFeature()"); + } - var sagaMetaModel = new SagaMetaModel(typeBasedSagas); + var sagaMetaModel = context.Settings.Get(); + sagaMetaModel.Initialize(context.Settings.GetAvailableTypes(), conventions); RegisterCustomFindersInContainer(context.Container, sagaMetaModel); - context.Container.RegisterSingleton(sagaMetaModel); - foreach (var t in context.Settings.GetAvailableTypes()) { if (IsSagaNotFoundHandler(t)) @@ -55,6 +56,10 @@ protected internal override void Setup(FeatureConfigurationContext context) } } + // Register the Saga related behaviors for incoming messages + context.Pipeline.Register("InvokeSaga", b => new SagaPersistenceBehavior(b.Build(), b.Build(), sagaMetaModel), "Invokes the saga logic"); + context.Pipeline.Register("InvokeSagaNotFound", new InvokeSagaNotFoundBehavior(), "Invokes saga not found logic"); + context.Pipeline.Register("AttachSagaDetailsToOutGoingMessage", new AttachSagaDetailsToOutGoingMessageBehavior(), "Makes sure that outgoing messages have saga info attached to them"); } static void RegisterCustomFindersInContainer(IConfigureComponents container, IEnumerable sagaMetaModel) @@ -72,13 +77,11 @@ static void RegisterCustomFindersInContainer(IConfigureComponents container, IEn } } - static bool IsSagaType(Type t) { return IsCompatible(t, typeof(Saga)); } - static bool IsSagaNotFoundHandler(Type t) { return IsCompatible(t, typeof(IHandleSagaNotFound)); @@ -97,8 +100,6 @@ static bool IsTypeATimeoutHandledByAnySaga(Type type, IEnumerable sagas) return sagas.Any(t => timeoutHandler.IsAssignableFrom(t) && !messageHandler.IsAssignableFrom(t)); } - - Conventions conventions; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/ToSagaExpression.cs b/src/NServiceBus.Core/Sagas/ToSagaExpression.cs new file mode 100644 index 00000000000..c907f10e097 --- /dev/null +++ b/src/NServiceBus.Core/Sagas/ToSagaExpression.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using System; + using System.Linq.Expressions; + + /// + /// Allows a more fluent way to map sagas. + /// + public class ToSagaExpression where TSagaData : IContainSagaData + { + /// + /// Initializes a new instance of . + /// + public ToSagaExpression(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration, Expression> messageProperty) + { + Guard.AgainstNull(nameof(sagaMessageFindingConfiguration), sagaMessageFindingConfiguration); + Guard.AgainstNull(nameof(messageProperty), messageProperty); + this.sagaMessageFindingConfiguration = sagaMessageFindingConfiguration; + this.messageProperty = messageProperty; + } + + + /// + /// Defines the property on the saga data to which the message property should be mapped. + /// + /// The property to map. + public void ToSaga(Expression> sagaEntityProperty) + { + Guard.AgainstNull(nameof(sagaEntityProperty), sagaEntityProperty); + sagaMessageFindingConfiguration.ConfigureMapping(sagaEntityProperty, messageProperty); + } + + Expression> messageProperty; + IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/TypeBasedSagaMetaModel.cs b/src/NServiceBus.Core/Sagas/TypeBasedSagaMetaModel.cs deleted file mode 100644 index ee70a5cfc63..00000000000 --- a/src/NServiceBus.Core/Sagas/TypeBasedSagaMetaModel.cs +++ /dev/null @@ -1,318 +0,0 @@ -namespace NServiceBus.Sagas -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using System.Runtime.Serialization; - using NServiceBus.Saga; - using NServiceBus.Utils.Reflection; - - class TypeBasedSagaMetaModel - { - public static IEnumerable Create(IList availableTypes, Conventions conventions) - { - return availableTypes.Where(IsSagaType) - .Select(t => Create(t, availableTypes, conventions)).ToList(); - } - - static bool IsSagaType(Type t) - { - return SagaType.IsAssignableFrom(t) && t != SagaType && !t.IsGenericType; - } - - static Type SagaType = typeof(Saga); - - static Type GetBaseSagaType(Type t) - { - var currentType = t.BaseType; - var previousType = t; - - while (currentType != null) - { - if (currentType == SagaType) - { - return previousType; - } - - previousType = currentType; - currentType = currentType.BaseType; - } - - throw new InvalidOperationException(); - } - - public static SagaMetadata Create(Type sagaType) - { - return Create(sagaType, new List(), new Conventions()); - } - - public static SagaMetadata Create(Type sagaType, IEnumerable availableTypes, Conventions conventions) - { - if (!IsSagaType(sagaType)) - { - throw new Exception(sagaType.FullName + " is not a saga"); - } - - var genericArguments = GetBaseSagaType(sagaType).GetGenericArguments(); - if (genericArguments.Length != 1) - { - throw new Exception(string.Format("'{0}' saga type does not implement Saga", sagaType)); - } - - var sagaEntityType = genericArguments.Single(); - var uniquePropertiesOnEntity = FindUniqueAttributes(sagaEntityType).ToList(); - - var mapper = new SagaMapper(); - - var saga = (Saga)FormatterServices.GetUninitializedObject(sagaType); - saga.ConfigureHowToFindSaga(mapper); - - ApplyScannedFinders(mapper, sagaEntityType, availableTypes, conventions); - - - var finders = new List(); - - foreach (var mapping in mapper.Mappings) - { - if (uniquePropertiesOnEntity.All(p => p.Name != mapping.SagaPropName)) - { - uniquePropertiesOnEntity.Add(new CorrelationProperty { Name = mapping.SagaPropName }); - } - - - SetFinderForMessage(mapping, sagaEntityType, finders); - } - - var associatedMessages = GetAssociatedMessages(sagaType) - .ToList(); - - var metadata = new SagaMetadata(associatedMessages, finders) - { - Name = sagaType.FullName, - EntityName = sagaEntityType.FullName, - CorrelationProperties = uniquePropertiesOnEntity, - SagaEntityType = sagaEntityType, - SagaType = sagaType - }; - return metadata; - } - - static void ApplyScannedFinders(SagaMapper mapper, Type sagaEntityType, IEnumerable availableTypes, Conventions conventions) - { - var actualFinders = availableTypes.Where(t => typeof(IFinder).IsAssignableFrom(t)) - .ToList(); - - foreach (var finderType in actualFinders) - { - foreach (var interfaceType in finderType.GetInterfaces()) - { - var args = interfaceType.GetGenericArguments(); - if (args.Length != 2) - { - continue; - } - - Type messageType = null; - Type entityType = null; - foreach (var type in args) - { - - if (typeof(IContainSagaData).IsAssignableFrom(type)) - { - entityType = type; - } - - if (conventions.IsMessageType(type) || type == typeof(object)) - { - messageType = type; - } - } - - if (entityType == null || messageType == null || entityType != sagaEntityType) - { - continue; - } - - var existingMapping = mapper.Mappings.SingleOrDefault(m => m.MessageType == messageType); - - if (existingMapping != null) - { - existingMapping.CustomFinderType = finderType; - } - else - { - mapper.ConfigureCustomFinder(finderType, messageType); - } - - - } - } - } - - static void SetFinderForMessage(SagaToMessageMap mapping, Type sagaEntityType, List finders) - { - var finder = new SagaFinderDefinition - { - MessageType = mapping.MessageType.FullName - }; - - if (mapping.CustomFinderType != null) - { - finder.Type = typeof(CustomFinderAdapter<,>).MakeGenericType(sagaEntityType, mapping.MessageType); - - finder.Properties["custom-finder-clr-type"] = mapping.CustomFinderType; - } - else - { - finder.Type = typeof(PropertySagaFinder<>).MakeGenericType(sagaEntityType); - finder.Properties["property-accessor"] = mapping.MessageProp; - finder.Properties["saga-property-name"] = mapping.SagaPropName; - - } - - finders.Add(finder); - } - - static IEnumerable GetAssociatedMessages(Type sagaType) - { - var result = GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IAmStartedByMessages<>)) - .Select(t => new SagaMessage(t.FullName, true)).ToList(); - - foreach (var messageType in GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IHandleMessages<>))) - { - if (result.Any(m => m.MessageType == messageType.FullName)) - { - continue; - } - result.Add(new SagaMessage(messageType.FullName, false)); - } - - foreach (var messageType in GetMessagesCorrespondingToFilterOnSaga(sagaType, typeof(IHandleTimeouts<>))) - { - if (result.Any(m => m.MessageType == messageType.FullName)) - { - continue; - } - result.Add(new SagaMessage(messageType.FullName, false)); - } - - return result; - } - - static IEnumerable GetMessagesCorrespondingToFilterOnSaga(Type sagaType, Type filter) - { - foreach (var interfaceType in sagaType.GetInterfaces()) - { - foreach (var argument in interfaceType.GetGenericArguments()) - { - var genericType = filter.MakeGenericType(argument); - var isOfFilterType = genericType == interfaceType; - if (!isOfFilterType) - { - continue; - } - yield return argument; - } - } - } - - static IEnumerable FindUniqueAttributes(Type sagaEntityType) - { - return UniqueAttribute.GetUniqueProperties(sagaEntityType).Select(pt => new CorrelationProperty{Name = pt.Name}); - } - - - - class SagaMapper : IConfigureHowToFindSagaWithMessage - { - public List Mappings = new List(); - - void IConfigureHowToFindSagaWithMessage.ConfigureMapping(Expression> sagaEntityProperty, Expression> messageExpression) - { - var sagaProp = Reflect.GetProperty(sagaEntityProperty, true); - - ValidateMapping(messageExpression, sagaProp); - - ThrowIfNotPropertyLambdaExpression(sagaEntityProperty, sagaProp); - var compiledMessageExpression = messageExpression.Compile(); - var messageFunc = new Func(o => compiledMessageExpression((TMessage)o)); - - Mappings.Add(new SagaToMessageMap - { - MessageProp = messageFunc, - SagaPropName = sagaProp.Name, - MessageType = typeof(TMessage) - }); - } - - static void ValidateMapping(Expression> messageExpression, PropertyInfo sagaProp) - { - if (sagaProp.Name.ToLower() != "id") - { - return; - } - - if (messageExpression.Body.NodeType != ExpressionType.Convert) - { - return; - } - - var memberExpr = ((UnaryExpression)messageExpression.Body).Operand as MemberExpression; - - if (memberExpr == null) - { - return; - } - - var propertyInfo = memberExpr.Member as PropertyInfo; - - var message = "Message properties mapped to the saga id needs to be of type Guid, please change property {0} on message {1} to a Guid"; - - if (propertyInfo != null) - { - if (propertyInfo.PropertyType != typeof(Guid)) - { - throw new Exception(string.Format(message, propertyInfo.Name, typeof(TMessage).Name)); - } - - return; - } - - - var fieldInfo = memberExpr.Member as FieldInfo; - - if (fieldInfo != null) - { - if (fieldInfo.FieldType != typeof(Guid)) - { - throw new Exception(string.Format(message, fieldInfo.Name, typeof(TMessage).Name)); - } - } - } - - public void ConfigureCustomFinder(Type finderType, Type messageType) - { - Mappings.Add(new SagaToMessageMap - { - MessageType = messageType, - CustomFinderType = finderType - }); - } - - // ReSharper disable once UnusedParameter.Local - void ThrowIfNotPropertyLambdaExpression(Expression> expression, PropertyInfo propertyInfo) - { - if (propertyInfo == null) - { - throw new ArgumentException( - String.Format( - "Only public properties are supported for mapping Sagas. The lambda expression provided '{0}' is not mapping to a Property!", - expression.Body)); - } - } - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Satellites/Config/SatelliteContext.cs b/src/NServiceBus.Core/Satellites/Config/SatelliteContext.cs deleted file mode 100644 index 79cbb25132d..00000000000 --- a/src/NServiceBus.Core/Satellites/Config/SatelliteContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus.Satellites.Config -{ - using Unicast.Transport; - - class SatelliteContext - { - internal SatelliteContext() - { - Instance = null; - Transport = null; - } - - public TransportReceiver Transport { get; set; } - public ISatellite Instance { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Satellites/IAdvancedSatellite.cs b/src/NServiceBus.Core/Satellites/IAdvancedSatellite.cs deleted file mode 100644 index 3022fef8593..00000000000 --- a/src/NServiceBus.Core/Satellites/IAdvancedSatellite.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus.Satellites -{ - using System; - using Unicast.Transport; - - /// - /// Interface for satellites that needs more control over how the receiver is being setup - /// - public interface IAdvancedSatellite : ISatellite - { - /// - /// Gets the customizations to apply to the receiver - /// - Action GetReceiverCustomization(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Satellites/ISatellite.cs b/src/NServiceBus.Core/Satellites/ISatellite.cs deleted file mode 100644 index e0310d2b6dd..00000000000 --- a/src/NServiceBus.Core/Satellites/ISatellite.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.Satellites -{ - using Unicast.Transport; - - /// - /// Implement this interface to create a Satellite. - /// - public interface ISatellite - { - /// - /// This method is called when a message is available to be processed. - /// - /// The received. - /// If false then will call - bool Handle(TransportMessage message); - - /// - /// The for this to use when receiving messages. - /// - Address InputAddress { get; } - - /// - /// Set to true to disable this . - /// - bool Disabled { get; } - - /// - /// Starts the . - /// - void Start(); - - /// - /// Stops the . - /// - void Stop(); - } -} diff --git a/src/NServiceBus.Core/Satellites/SatelliteLauncher.cs b/src/NServiceBus.Core/Satellites/SatelliteLauncher.cs deleted file mode 100644 index ad26e1c61ec..00000000000 --- a/src/NServiceBus.Core/Satellites/SatelliteLauncher.cs +++ /dev/null @@ -1,126 +0,0 @@ -namespace NServiceBus.Satellites -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Config; - using Logging; - using ObjectBuilder; - using Unicast.Transport; - - class SatelliteLauncher - { - IBuilder builder; - - public SatelliteLauncher(IBuilder builder) - { - this.builder = builder; - } - - public void Start() - { - var satellitesList = builder.BuildAll() - .ToList() - .Where(s => !s.Disabled) - .ToList(); - - - var satelliteContexts = new SatelliteContext[satellitesList.Count]; - - Parallel.For(0, satellitesList.Count, index => - { - var satellite = satellitesList[index]; - - Logger.DebugFormat("Starting {1}/{2} {0} satellite", satellite.GetType().AssemblyQualifiedName, - index + 1, satellitesList.Count); - - var satelliteContext = new SatelliteContext - { - Instance = satellite - }; - - if (satellite.InputAddress != null) - { - satelliteContext.Transport = builder.Build(); - - var advancedSatellite = satellite as IAdvancedSatellite; - if (advancedSatellite != null) - { - var receiverCustomization = advancedSatellite.GetReceiverCustomization(); - - receiverCustomization(satelliteContext.Transport); - } - } - - StartSatellite(satelliteContext); - - satelliteContexts[index] = satelliteContext; - - Logger.InfoFormat("Started {1}/{2} {0} satellite", satellite.GetType().AssemblyQualifiedName, - index + 1, satellitesList.Count); - - }); - - satellites.AddRange(satelliteContexts); - } - - public void Stop() - { - Parallel.ForEach(satellites, (context, state, index) => - { - Logger.DebugFormat("Stopping {1}/{2} {0} satellite", context.Instance.GetType().AssemblyQualifiedName, - index + 1, satellites.Count); - - if (context.Transport != null) - { - context.Transport.Stop(); - } - - context.Instance.Stop(); - - Logger.InfoFormat("Stopped {1}/{2} {0} satellite", context.Instance.GetType().AssemblyQualifiedName, - index + 1, satellites.Count); - }); - } - - void HandleMessageReceived(object sender, TransportMessageReceivedEventArgs e, ISatellite satellite) - { - if (!satellite.Handle(e.Message)) - { - ((ITransport) sender).AbortHandlingCurrentMessage(); - } - } - - void StartSatellite(SatelliteContext context) - { - Logger.DebugFormat("Starting satellite {0} for {1}.", context.Instance.GetType().AssemblyQualifiedName, - context.Instance.InputAddress); - - try - { - if (context.Transport != null) - { - context.Transport.TransportMessageReceived += (o, e) => HandleMessageReceived(o, e, context.Instance); - context.Transport.Start(context.Instance.InputAddress); - } - else - { - Logger.DebugFormat("No input queue configured for {0}", context.Instance.GetType().AssemblyQualifiedName); - } - - context.Instance.Start(); - } - catch (Exception ex) - { - Logger.Fatal(string.Format("Satellite {0} failed to start.", context.Instance.GetType().AssemblyQualifiedName), ex); - - throw; - } - } - - static ILog Logger = LogManager.GetLogger(); - - readonly List satellites = new List(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Satellites/SatellitesQueuesCreator.cs b/src/NServiceBus.Core/Satellites/SatellitesQueuesCreator.cs deleted file mode 100644 index 1c3b3b6552d..00000000000 --- a/src/NServiceBus.Core/Satellites/SatellitesQueuesCreator.cs +++ /dev/null @@ -1,34 +0,0 @@ - -namespace NServiceBus.Satellites -{ - using System.Linq; - using Installation; - using Transports; - - class SatellitesQueuesCreator : INeedToInstallSomething - { - public ICreateQueues QueueCreator { get; set; } - - public void Install(string identity, Configure config) - { - if (config.Settings.Get("Endpoint.SendOnly")) - { - return; - } - - if (!config.CreateQueues()) - { - return; - } - - var satellites = config.Builder - .BuildAll() - .ToList(); - - foreach (var satellite in satellites.Where(satellite => !satellite.Disabled && satellite.InputAddress != null)) - { - QueueCreator.CreateQueueIfNecessary(satellite.InputAddress, identity); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/DefaultScheduler.cs b/src/NServiceBus.Core/Scheduling/DefaultScheduler.cs index 7534c2900b7..c6bcd38cf76 100644 --- a/src/NServiceBus.Core/Scheduling/DefaultScheduler.cs +++ b/src/NServiceBus.Core/Scheduling/DefaultScheduler.cs @@ -1,79 +1,70 @@ -namespace NServiceBus.Scheduling +namespace NServiceBus { using System; using System.Collections.Concurrent; using System.Diagnostics; - using System.Threading; using System.Threading.Tasks; using Logging; class DefaultScheduler { - public DefaultScheduler(IBus bus) - { - this.bus = bus; - } - public void Schedule(TaskDefinition taskDefinition) { scheduledTasks[taskDefinition.Id] = taskDefinition; - logger.DebugFormat("Task {0}/{1} scheduled with timeSpan {2}", taskDefinition.Name, taskDefinition.Id, taskDefinition.Every); - DeferTask(taskDefinition); } - public void Start(Guid taskId) + public async Task Start(Guid taskId, IPipelineContext context) { TaskDefinition taskDefinition; if (!scheduledTasks.TryGetValue(taskId, out taskDefinition)) { - logger.InfoFormat("Could not find any scheduled task {0} with with Id. The DefaultScheduler does not persist tasks between restarts.", taskId); + logger.InfoFormat("Could not find any scheduled task with id {0}. The DefaultScheduler does not persist tasks between restarts.", taskId); return; } - DeferTask(taskDefinition); - ExecuteTask(taskDefinition); + await DeferTask(taskDefinition, context).ConfigureAwait(false); + await ExecuteTask(taskDefinition, context).ConfigureAwait(false); } - static void ExecuteTask(TaskDefinition taskDefinition) + static async Task ExecuteTask(TaskDefinition taskDefinition, IPipelineContext context) { - logger.InfoFormat("Start executing scheduled task {0}", taskDefinition.Name); + logger.InfoFormat("Start executing scheduled task named '{0}'.", taskDefinition.Name); var sw = new Stopwatch(); sw.Start(); - Task.Factory - .StartNew(taskDefinition.Task, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) - .ContinueWith(task => - { - sw.Stop(); - - if (task.IsFaulted) - { - task.Exception.Handle(ex => - { - logger.Error(String.Format("Failed to execute scheduled task {0}", taskDefinition.Name), ex); - return true; - }); - } - else - { - logger.InfoFormat("Scheduled task {0} run for {1}", taskDefinition.Name, sw.Elapsed.ToString()); - } - }); + try + { + await taskDefinition.Task(context).ConfigureAwait(false); + logger.InfoFormat("Scheduled task '{0}' run for {1}", taskDefinition.Name, sw.Elapsed); + } + catch (Exception ex) + { + logger.Error($"Failed to execute scheduled task '{taskDefinition.Name}'.", ex); + } + finally + { + sw.Stop(); + } } - void DeferTask(TaskDefinition taskDefinition) + static Task DeferTask(TaskDefinition taskDefinition, IPipelineContext context) { - bus.Defer(taskDefinition.Every, new Messages.ScheduledTask + var options = new SendOptions(); + + options.DelayDeliveryWith(taskDefinition.Every); + options.RouteToThisEndpoint(); + + return context.Send(new ScheduledTask { TaskId = taskDefinition.Id, Name = taskDefinition.Name, Every = taskDefinition.Every - }); + }, options); } + ConcurrentDictionary scheduledTasks = new ConcurrentDictionary(); + static ILog logger = LogManager.GetLogger(); - IBus bus; - internal ConcurrentDictionary scheduledTasks = new ConcurrentDictionary(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/InMemoryScheduledTaskStorage.cs b/src/NServiceBus.Core/Scheduling/InMemoryScheduledTaskStorage.cs deleted file mode 100644 index 83b77049b84..00000000000 --- a/src/NServiceBus.Core/Scheduling/InMemoryScheduledTaskStorage.cs +++ /dev/null @@ -1,27 +0,0 @@ -//namespace NServiceBus.Scheduling -//{ -// using System; -// using System.Collections.Concurrent; -// using System.Collections.Generic; - -// class InMemoryScheduledTaskStorage -// { - -// public void Add(ScheduledTask scheduledTask) -// { -// scheduledTasks.Add(scheduledTask.Id, scheduledTask); -// } - -// public ScheduledTask Get(Guid taskId) -// { -// ScheduledTask task; -// scheduledTasks.TryGetValue(taskId, out task); -// return task; -// } - -// public IDictionary Tasks -// { -// get { return scheduledTasks; } -// } -// } -//} \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/Messages/ScheduledTask.cs b/src/NServiceBus.Core/Scheduling/Messages/ScheduledTask.cs index af8c957c49a..f64250759bc 100644 --- a/src/NServiceBus.Core/Scheduling/Messages/ScheduledTask.cs +++ b/src/NServiceBus.Core/Scheduling/Messages/ScheduledTask.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Scheduling.Messages +namespace NServiceBus { using System; diff --git a/src/NServiceBus.Core/Scheduling/Schedule.cs b/src/NServiceBus.Core/Scheduling/Schedule.cs deleted file mode 100644 index edd7ecfb8c7..00000000000 --- a/src/NServiceBus.Core/Scheduling/Schedule.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Linq; - using System.Threading; - using ObjectBuilder; - using Scheduling; - - /// - /// Scheduling capability to schedule a task (as an ) to be executed repeatedly in a given interval. - /// - /// This is a in-memory list of s. - public partial class Schedule - { - IBuilder builder; - - /// - /// Builds a new instance of . - /// - /// The . - public Schedule(IBuilder builder) - { - this.builder = builder; - } - - /// - /// Schedules a task to be executed repeatedly in a given interval. - /// - /// The interval to repeatedly execute the . - /// The to execute. - public void Every(TimeSpan timeSpan, Action task) - { - var declaringType = task.Method.DeclaringType; - - while (declaringType.DeclaringType != null && - declaringType.CustomAttributes.Any(a => a.AttributeType.Name == "CompilerGeneratedAttribute")) - { - declaringType = declaringType.DeclaringType; - } - - Every(timeSpan, declaringType.Name, task); - } - - /// - /// Schedules a task to be executed repeatedly in a given interval. - /// - /// The interval to repeatedly execute the . - /// The to execute. - /// The name to use used for logging inside the new . - public void Every(TimeSpan timeSpan, string name, Action task) - { - builder.Build() - .Schedule(new TaskDefinition - { - Every = timeSpan, - Name = name, - Task = task - }); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/ScheduleBehavior.cs b/src/NServiceBus.Core/Scheduling/ScheduleBehavior.cs new file mode 100644 index 00000000000..c1bc44ed727 --- /dev/null +++ b/src/NServiceBus.Core/Scheduling/ScheduleBehavior.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class ScheduleBehavior : IBehavior + { + public ScheduleBehavior(DefaultScheduler scheduler) + { + this.scheduler = scheduler; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + State state; + if (context.Extensions.TryGet(out state)) + { + scheduler.Schedule(state.TaskDefinition); + } + return next(context); + } + + DefaultScheduler scheduler; + + public class State + { + public TaskDefinition TaskDefinition { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/ScheduleExtensions.cs b/src/NServiceBus.Core/Scheduling/ScheduleExtensions.cs new file mode 100644 index 00000000000..db0b06c9d65 --- /dev/null +++ b/src/NServiceBus.Core/Scheduling/ScheduleExtensions.cs @@ -0,0 +1,100 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Logging; + + /// + /// Extends the context with scheduling capabilities. + /// + public static class ScheduleExtensions + { + /// + /// Schedules a task to be executed repeatedly in a given interval. + /// + /// The session which allows you to perform message operation. + /// The interval to repeatedly execute the . + /// The to execute. + [ObsoleteEx(ReplacementTypeOrMember = "ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, Func task)", TreatAsErrorFromVersion = "6", RemoveInVersion = "7")] + public static void ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, Action task) + { + throw new NotImplementedException(); + } + + /// + /// Schedules a task to be executed repeatedly in a given interval. + /// + /// The session which allows you to perform message operation. + /// The interval to repeatedly execute the . + /// The to execute. + /// The name to use used for logging inside the new . + [ObsoleteEx(ReplacementTypeOrMember = "ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, string name, Func task)", TreatAsErrorFromVersion = "6", RemoveInVersion = "7")] + public static void ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, string name, Action task) + { + throw new NotImplementedException(); + } + + /// + /// Schedules a task to be executed repeatedly in a given interval. + /// + /// The session which allows you to perform message operation. + /// The interval to repeatedly execute the . + /// The async function to execute. + public static Task ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, Func task) + { + Guard.AgainstNull(nameof(task), task); + Guard.AgainstNegativeAndZero(nameof(timeSpan), timeSpan); + + var declaringType = task.Method.DeclaringType; + while (declaringType.DeclaringType != null && + declaringType.CustomAttributes.Any(a => a.AttributeType.Name == "CompilerGeneratedAttribute")) + { + declaringType = declaringType.DeclaringType; + } + + return ScheduleEvery(session, timeSpan, declaringType.Name, task); + } + + /// + /// Schedules a task to be executed repeatedly in a given interval. + /// + /// The session which allows you to perform message operation. + /// The interval to repeatedly execute the . + /// The async function to execute. + /// The name to used for logging the task being executed. + public static Task ScheduleEvery(this IMessageSession session, TimeSpan timeSpan, string name, Func task) + { + Guard.AgainstNull(nameof(task), task); + Guard.AgainstNullAndEmpty(nameof(name), name); + Guard.AgainstNegativeAndZero(nameof(timeSpan), timeSpan); + + return Schedule(session, new TaskDefinition + { + Every = timeSpan, + Name = name, + Task = task + }); + } + + static Task Schedule(IMessageSession session, TaskDefinition taskDefinition) + { + logger.DebugFormat("Task '{0}' (with id {1}) scheduled with timeSpan {2}", taskDefinition.Name, taskDefinition.Id, taskDefinition.Every); + + var options = new SendOptions(); + options.DelayDeliveryWith(taskDefinition.Every); + options.RouteToThisEndpoint(); + options.Context.GetOrCreate().TaskDefinition = taskDefinition; + + return session.Send(new ScheduledTask + { + TaskId = taskDefinition.Id, + Name = taskDefinition.Name, + Every = taskDefinition.Every + }, options); + } + + static ILog logger = LogManager.GetLogger(); //Intentionally using different type. + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/Schedule_Obsolete.cs b/src/NServiceBus.Core/Scheduling/Schedule_Obsolete.cs deleted file mode 100644 index b167a35a056..00000000000 --- a/src/NServiceBus.Core/Scheduling/Schedule_Obsolete.cs +++ /dev/null @@ -1,37 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public partial class Schedule - { - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0", - Message = "Inject an instance of `Schedule` to your class and then call the non-static version of `Schedule.Every(TimeSpan timeSpan, Action task)`.")] - public static Schedule Every(TimeSpan timeSpan) - { - throw new NotImplementedException("Api has been obsolete."); - } - - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0", - Message = "Inject an instance of `Schedule` to your class and then call the non static member `Schedule.Every(TimeSpan timeSpan, Action task)`.")] - public void Action(Action task) - { - throw new NotImplementedException("Api has been obsolete."); - } - - [ObsoleteEx( - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0", - Message = "Inject an instance of `Schedule` to your class thenuse the non-static version of `Schedule.Every(TimeSpan timeSpan, string name, Action task)`.")] - public void Action(string name, Action task) - { - throw new NotImplementedException("Api has been obsolete."); - } - - } -} diff --git a/src/NServiceBus.Core/Scheduling/ScheduledTaskMessageHandler.cs b/src/NServiceBus.Core/Scheduling/ScheduledTaskMessageHandler.cs index ac2124daad7..ffca3886c04 100644 --- a/src/NServiceBus.Core/Scheduling/ScheduledTaskMessageHandler.cs +++ b/src/NServiceBus.Core/Scheduling/ScheduledTaskMessageHandler.cs @@ -1,17 +1,19 @@ -namespace NServiceBus.Scheduling +namespace NServiceBus { - class ScheduledTaskMessageHandler : IHandleMessages - { - DefaultScheduler scheduler; + using System.Threading.Tasks; + class ScheduledTaskMessageHandler : IHandleMessages + { public ScheduledTaskMessageHandler(DefaultScheduler scheduler) { this.scheduler = scheduler; } - public void Handle(Messages.ScheduledTask message) + public Task Handle(ScheduledTask message, IMessageHandlerContext context) { - scheduler.Start(message.TaskId); + return scheduler.Start(message.TaskId, context); } + + DefaultScheduler scheduler; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/Scheduler.cs b/src/NServiceBus.Core/Scheduling/Scheduler.cs index 46495d41b43..c4d9d30798b 100644 --- a/src/NServiceBus.Core/Scheduling/Scheduler.cs +++ b/src/NServiceBus.Core/Scheduling/Scheduler.cs @@ -1,10 +1,8 @@ namespace NServiceBus.Features { - using Scheduling; - using ScheduledTask = Scheduling.Messages.ScheduledTask; - /// - /// NServiceBus scheduling capability you can schedule a task or an action/lambda, to be executed repeatedly in a given interval. + /// NServiceBus scheduling capability you can schedule a task or an action/lambda, to be executed repeatedly in a given + /// interval. /// public class Scheduler : Feature { @@ -14,15 +12,18 @@ internal Scheduler() EnableByDefault(); } + /// - /// Invoked if the feature is activated + /// Invoked if the feature is activated. /// - /// The feature context + /// The feature context. protected internal override void Setup(FeatureConfigurationContext context) { context.Settings.Get().AddSystemMessagesConventions(t => typeof(ScheduledTask).IsAssignableFrom(t)); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); + + var defaultScheduler = new DefaultScheduler(); + context.Container.RegisterSingleton(defaultScheduler); + context.Pipeline.Register("ScheduleBehavior", new ScheduleBehavior(defaultScheduler), "Registers a task definition for scheduling."); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Scheduling/TaskDefinition.cs b/src/NServiceBus.Core/Scheduling/TaskDefinition.cs index 713686f617b..cd3b8260fe5 100644 --- a/src/NServiceBus.Core/Scheduling/TaskDefinition.cs +++ b/src/NServiceBus.Core/Scheduling/TaskDefinition.cs @@ -1,6 +1,7 @@ -namespace NServiceBus.Scheduling +namespace NServiceBus { using System; + using System.Threading.Tasks; class TaskDefinition { @@ -11,7 +12,7 @@ public TaskDefinition() public Guid Id { get; private set; } public string Name { get; set; } - public Action Task { get; set; } + public Func Task { get; set; } public TimeSpan Every { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfig.cs b/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfig.cs deleted file mode 100644 index 1ce7ac96372..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfig.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.Config -{ - using System; - using System.Configuration; - using NServiceBus.SecondLevelRetries; - - /// - /// Configuration options for the SLR feature - /// - public class SecondLevelRetriesConfig : ConfigurationSection - { - /// - /// Creates an instance of . - /// - public SecondLevelRetriesConfig() - { - Properties.Add(new ConfigurationProperty("Enabled", typeof(bool), true)); - Properties.Add(new ConfigurationProperty("TimeIncrease", typeof(TimeSpan), SecondLevelRetriesConfiguration.DefaultTimeIncrease, null, new TimeSpanValidator(TimeSpan.Zero, TimeSpan.MaxValue), ConfigurationPropertyOptions.None)); - Properties.Add(new ConfigurationProperty("NumberOfRetries", typeof(int), SecondLevelRetriesConfiguration.DefaultNumberOfRetries, null, new IntegerValidator(0, Int32.MaxValue), ConfigurationPropertyOptions.None)); - } - - /// - /// True if SLR should be used - /// - public bool Enabled - { - get { return (bool)this["Enabled"]; } - set { this["Enabled"] = value; } - } - - /// - /// Sets the time to increase the delay between retries - /// - public TimeSpan TimeIncrease - { - get { return (TimeSpan) this["TimeIncrease"]; } - set { this["TimeIncrease"] = value; } - } - - /// - /// Sets the number of retries to do before aborting and sending the message to the error queue - /// - public int NumberOfRetries - { - get { return (int)this["NumberOfRetries"]; } - set { this["NumberOfRetries"] = value; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfigExtensions.cs b/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfigExtensions.cs deleted file mode 100644 index 8aae63bd56e..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesConfigExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus -{ - using System; - using SecondLevelRetries.Config; - - /// - /// Provides config options for the SLR feature - /// - public static class SecondLevelRetriesConfigExtensions - { - /// - /// Allows for customization of the second level retries - /// - [ObsoleteEx( - Message = "Use `configuration.SecondLevelRetries().CustomRetryPolicy()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] -// ReSharper disable UnusedParameter.Global - public static Configure SecondLevelRetries(this Configure config, Action customSettings) -// ReSharper restore UnusedParameter.Global - { - throw new InvalidOperationException(); - } - - /// - /// Allows for customization of the second level retries - /// - public static SecondLevelRetriesSettings SecondLevelRetries(this BusConfiguration config) - { - return new SecondLevelRetriesSettings(config); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesSettings.cs b/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesSettings.cs deleted file mode 100644 index 28fdbbe6505..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/Config/SecondLevelRetriesSettings.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus.SecondLevelRetries.Config -{ - using System; - - /// - /// Configuration settings for second level retries - /// - public class SecondLevelRetriesSettings - { - readonly BusConfiguration config; - - internal SecondLevelRetriesSettings(BusConfiguration config) - { - this.config = config; - } - - /// - /// Register a custom retry policy - /// - public void CustomRetryPolicy(Func customPolicy) - { - config.Settings.Set("SecondLevelRetries.RetryPolicy", customPolicy); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/Helpers/SecondLevelRetriesHeaders.cs b/src/NServiceBus.Core/SecondLevelRetries/Helpers/SecondLevelRetriesHeaders.cs deleted file mode 100644 index 07b55c0dcaf..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/Helpers/SecondLevelRetriesHeaders.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NServiceBus.SecondLevelRetries.Helpers -{ - static class SecondLevelRetriesHeaders - { - public const string RetriesTimestamp = "NServiceBus.Retries.Timestamp"; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/Helpers/TransportMessageHelpers_Obsolete.cs b/src/NServiceBus.Core/SecondLevelRetries/Helpers/TransportMessageHelpers_Obsolete.cs deleted file mode 100644 index 7fccaca7fdf..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/Helpers/TransportMessageHelpers_Obsolete.cs +++ /dev/null @@ -1,59 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus.SecondLevelRetries.Helpers -{ - using System; - - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class TransportMessageHelpers - { - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly using the `FaultsHeaderKeys.FailedQ` key", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Address GetAddressOfFaultingEndpoint(TransportMessage message) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static string GetHeader(TransportMessage message, string key) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static bool HeaderExists(TransportMessage message, string key) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static void SetHeader(TransportMessage message, string key, string value) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Access the `TransportMessage.Headers` dictionary directly using the `Headers.Retries` key", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static int GetNumberOfRetries(TransportMessage message) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetries.cs b/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetries.cs deleted file mode 100644 index 6b0f6392b2d..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetries.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using Config; - using Faults.Forwarder; - using NServiceBus.SecondLevelRetries; - - /// - /// Used to configure Second Level Retries. - /// - public class SecondLevelRetries : Feature - { - internal SecondLevelRetries() - { - EnableByDefault(); - DependsOn(); - - Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"), "Send only endpoints can't use SLR since it requires receive capabilities"); - - Prerequisite(IsEnabledInConfig, "SLR was disabled in config"); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - var processorAddress = context.Settings.LocalAddress().SubScope("Retries"); - var useRemoteRetryProcessor = context.Settings.HasSetting("SecondLevelRetries.AddressOfRetryProcessor"); - - if (useRemoteRetryProcessor) - { - processorAddress = context.Settings.Get
("SecondLevelRetries.AddressOfRetryProcessor"); - } - - var container = context.Container; - var retryPolicy = context.Settings.GetOrDefault>("SecondLevelRetries.RetryPolicy"); - - var secondLevelRetriesConfiguration = new SecondLevelRetriesConfiguration(); - if (retryPolicy != null) - { - secondLevelRetriesConfiguration.RetryPolicy = retryPolicy; - } - - container.ConfigureProperty(fm => fm.RetriesQueue, processorAddress) - .ConfigureProperty(fm => fm.SecondLevelRetriesConfiguration, secondLevelRetriesConfiguration); - - container.ConfigureProperty(p => p.InputAddress, processorAddress) - .ConfigureProperty(p => p.SecondLevelRetriesConfiguration, secondLevelRetriesConfiguration) - .ConfigureProperty(p => p.Disabled, useRemoteRetryProcessor); - - var retriesConfig = context.Settings.GetConfigSection(); - if (retriesConfig == null) - { - return; - } - - secondLevelRetriesConfiguration.NumberOfRetries = retriesConfig.NumberOfRetries; - - if (retriesConfig.TimeIncrease != TimeSpan.MinValue) - { - secondLevelRetriesConfiguration.TimeIncrease = retriesConfig.TimeIncrease; - } - } - - bool IsEnabledInConfig(FeatureConfigurationContext context) - { - var retriesConfig = context.Settings.GetConfigSection(); - - if (retriesConfig == null) - return true; - - if (retriesConfig.NumberOfRetries == 0) - return false; - - return retriesConfig.Enabled; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesConfiguration.cs b/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesConfiguration.cs deleted file mode 100644 index 829b850f335..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesConfiguration.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace NServiceBus.SecondLevelRetries -{ - using System; - using NServiceBus.SecondLevelRetries.Helpers; - - class SecondLevelRetriesConfiguration - { - public SecondLevelRetriesConfiguration() - { - TimeIncrease = DefaultTimeIncrease; - NumberOfRetries = DefaultNumberOfRetries; - RetryPolicy = DefaultRetryPolicy; - } - - public Func RetryPolicy { get; set; } - public int NumberOfRetries { get; set; } - public TimeSpan TimeIncrease { get; set; } - - TimeSpan DefaultRetryPolicy(TransportMessage message) - { - if (HasReachedMaxTime(message)) - { - return TimeSpan.MinValue; - } - - var numberOfRetries = TransportMessageHeaderHelper.GetNumberOfRetries(message); - - var timeToIncreaseInTicks = TimeIncrease.Ticks*(numberOfRetries + 1); - var timeIncrease = TimeSpan.FromTicks(timeToIncreaseInTicks); - - return numberOfRetries >= NumberOfRetries ? TimeSpan.MinValue : timeIncrease; - } - - static bool HasReachedMaxTime(TransportMessage message) - { - var timestampHeader = TransportMessageHeaderHelper.GetHeader(message, SecondLevelRetriesHeaders.RetriesTimestamp); - - if (String.IsNullOrEmpty(timestampHeader)) - { - return false; - } - - try - { - var handledAt = DateTimeExtensions.ToUtcDateTime(timestampHeader); - - if (DateTime.UtcNow > handledAt.AddDays(1)) - { - return true; - } - } - // ReSharper disable once EmptyGeneralCatchClause - // this code won't usually throw but in case a user has decided to hack a message/headers and for some bizarre reason - // they changed the date and that parse fails, we want to make sure that doesn't prevent the message from being - // forwarded to the error queue. - catch (Exception) - { - } - - return false; - } - - public static int DefaultNumberOfRetries = 3; - public static TimeSpan DefaultTimeIncrease = TimeSpan.FromSeconds(10); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesProcessor.cs b/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesProcessor.cs deleted file mode 100644 index ce273e966f5..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/SecondLevelRetriesProcessor.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace NServiceBus.SecondLevelRetries -{ - using System; - using System.Globalization; - using NServiceBus.Faults.Forwarder; - using NServiceBus.Logging; - using NServiceBus.Satellites; - using NServiceBus.SecondLevelRetries.Helpers; - using NServiceBus.Transports; - using NServiceBus.Unicast; - - class SecondLevelRetriesProcessor : ISatellite - { - public SecondLevelRetriesProcessor() - { - Disabled = true; - } - - public SecondLevelRetriesConfiguration SecondLevelRetriesConfiguration { get; set; } - public ISendMessages MessageSender { get; set; } - public IDeferMessages MessageDeferrer { get; set; } - public FaultManager FaultManager { get; set; } - public Address InputAddress { get; set; } - public bool Disabled { get; set; } - - public void Start() - { - } - - public void Stop() - { - } - - public bool Handle(TransportMessage message) - { - // ---------------------------------------------------------------------------------- - // This check has now been moved to FaultManager. - // However the check remains here for backwards compatibility - // with messages that could be in the retries queue. - var defer = SecondLevelRetriesConfiguration.RetryPolicy.Invoke(message); - - if (defer < TimeSpan.Zero) - { - SendToErrorQueue(message); - return true; - } - // ---------------------------------------------------------------------------------- - - Defer(defer, message); - - return true; - } - - void SendToErrorQueue(TransportMessage message) - { - logger.ErrorFormat( - "SLR has failed to resolve the issue with message {0} and will be forwarded to the error queue at {1}", - message.Id, FaultManager.ErrorQueue); - - message.Headers.Remove(Headers.Retries); - - MessageSender.Send(message, new SendOptions(FaultManager.ErrorQueue)); - } - - void Defer(TimeSpan defer, TransportMessage message) - { - var retryMessageAt = DateTime.UtcNow + defer; - - TransportMessageHeaderHelper.SetHeader(message, Headers.Retries, - (TransportMessageHeaderHelper.GetNumberOfRetries(message) + 1).ToString(CultureInfo.InvariantCulture)); - - var addressOfFaultingEndpoint = TransportMessageHeaderHelper.GetAddressOfFaultingEndpoint(message); - - if (!TransportMessageHeaderHelper.HeaderExists(message, SecondLevelRetriesHeaders.RetriesTimestamp)) - { - TransportMessageHeaderHelper.SetHeader(message, SecondLevelRetriesHeaders.RetriesTimestamp, - DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow)); - } - - logger.DebugFormat("Defer message and send it to {0}", addressOfFaultingEndpoint); - - var sendOptions = new SendOptions(addressOfFaultingEndpoint) - { - DeliverAt = retryMessageAt - }; - - MessageDeferrer.Defer(message, sendOptions); - } - - static ILog logger = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SecondLevelRetries/TransportMessageHelpers.cs b/src/NServiceBus.Core/SecondLevelRetries/TransportMessageHelpers.cs deleted file mode 100644 index ce1ff5c5a77..00000000000 --- a/src/NServiceBus.Core/SecondLevelRetries/TransportMessageHelpers.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace NServiceBus.SecondLevelRetries.Helpers -{ - using System; - using Faults; - - static class TransportMessageHeaderHelper - { - public static Address GetAddressOfFaultingEndpoint(TransportMessage message) - { - var failedQ = GetHeader(message, FaultsHeaderKeys.FailedQ); - if (string.IsNullOrEmpty(failedQ)) - { - throw new Exception("Could not find address"); - } - - return Address.Parse(failedQ); - } - - public static string GetHeader(TransportMessage message, string key) - { - string value; - message.Headers.TryGetValue(key, out value); - return value; - } - - public static bool HeaderExists(TransportMessage message, string key) - { - return message.Headers.ContainsKey(key); - } - - public static void SetHeader(TransportMessage message, string key, string value) - { - if (message.Headers.ContainsKey(key)) - { - message.Headers[key] = value; - } - else - { - message.Headers.Add(key, value); - } - } - - public static int GetNumberOfRetries(TransportMessage message) - { - string value; - if (message.Headers.TryGetValue(Headers.Retries, out value)) - { - int i; - if (int.TryParse(value, out i)) - { - return i; - } - } - return 0; - } - } -} diff --git a/src/NServiceBus.Core/SendOptions.cs b/src/NServiceBus.Core/SendOptions.cs new file mode 100644 index 00000000000..140e7d8f8cf --- /dev/null +++ b/src/NServiceBus.Core/SendOptions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using Extensibility; + + /// + /// Allows the users to control how the send is performed. + /// + public class SendOptions : ExtendableOptions + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/CustomSerialization.cs b/src/NServiceBus.Core/Serialization/CustomSerialization.cs deleted file mode 100644 index 6a62828453b..00000000000 --- a/src/NServiceBus.Core/Serialization/CustomSerialization.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using NServiceBus.MessageInterfaces.MessageMapper.Reflection; - - class CustomSerialization : Feature - { - public CustomSerialization() - { - EnableByDefault(); - Prerequisite(this.ShouldSerializationFeatureBeEnabled, "CustomSerialization not enable since serialization definition not detected."); - } - - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(context.Settings.Get("CustomSerializerType"), DependencyLifecycle.SingleInstance); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/CustomSerializer.cs b/src/NServiceBus.Core/Serialization/CustomSerializer.cs deleted file mode 100644 index e7812976581..00000000000 --- a/src/NServiceBus.Core/Serialization/CustomSerializer.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Features; - using NServiceBus.Serialization; - - class CustomSerializer : SerializationDefinition - { - protected internal override Type ProvidedByFeature() - { - return typeof(CustomSerialization); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/IMessageSerializer.cs b/src/NServiceBus.Core/Serialization/IMessageSerializer.cs index 76b5af601c6..a20cb9aff2f 100644 --- a/src/NServiceBus.Core/Serialization/IMessageSerializer.cs +++ b/src/NServiceBus.Core/Serialization/IMessageSerializer.cs @@ -9,24 +9,27 @@ namespace NServiceBus.Serialization ///
public interface IMessageSerializer { + /// + /// Gets the content type into which this serializer serializes the content to. + /// + string ContentType { get; } + /// /// Serializes the given set of messages into the given stream. /// /// Message to serialize. - /// Stream for to be serialized into. + /// Stream for to be serialized into. void Serialize(object message, Stream stream); /// /// Deserializes from the given stream a set of messages. /// /// Stream that contains messages. - /// The list of message types to deserialize. If null the types must be inferred from the serialized data. + /// + /// The list of message types to deserialize. If null the types must be inferred from the + /// serialized data. + /// /// Deserialized messages. object[] Deserialize(Stream stream, IList messageTypes = null); - - /// - /// Gets the content type into which this serializer serializes the content to - /// - string ContentType { get; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationConfigExtensions.cs b/src/NServiceBus.Core/Serialization/SerializationConfigExtensions.cs index 06f4ec5c6d1..4a4e61f31a6 100644 --- a/src/NServiceBus.Core/Serialization/SerializationConfigExtensions.cs +++ b/src/NServiceBus.Core/Serialization/SerializationConfigExtensions.cs @@ -1,67 +1,80 @@ namespace NServiceBus { using System; - using NServiceBus.Settings; + using Configuration.AdvanceExtensibility; using Serialization; + using Settings; /// - /// Provides configuration options for serialization + /// Provides configuration options for serialization. /// - public static class SerializationConfigExtensions + public static partial class SerializationConfigExtensions { /// - /// Configures the given serializer to be used + /// Configures the given serializer to be used. /// - /// The serializer definition eg , , etc - /// - public static SerializationExtentions UseSerialization(this BusConfiguration config) where T : SerializationDefinition + /// The serializer definition eg , , etc. + /// The instance to apply the settings to. + public static SerializationExtensions UseSerialization(this EndpointConfiguration config) where T : SerializationDefinition, new() { - var type = typeof(SerializationExtentions<>).MakeGenericType(typeof(T)); - var extension = (SerializationExtentions)Activator.CreateInstance(type, config.Settings); - var definition = (SerializationDefinition)Activator.CreateInstance(typeof(T)); + Guard.AgainstNull(nameof(config), config); + var definition = (T) Activator.CreateInstance(typeof(T)); - config.Settings.Set("SelectedSerializer", definition); - - return extension; + return UseSerialization(config, definition); } /// - /// Configures the given serializer to be used + /// Configures the given serializer to be used. /// - /// - /// The custom serializer type to use for serialization that implements or a derived type from . - public static void UseSerialization(this BusConfiguration config, Type serializerType) + /// The serializer definition eg , , etc. + /// The instance to apply the settings to. + /// An instance of serialization definition. + public static SerializationExtensions UseSerialization(this EndpointConfiguration config, T serializationDefinition) where T : SerializationDefinition { - if (serializerType == null) - { - throw new ArgumentNullException("serializerType"); - } + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(serializationDefinition), serializationDefinition); - if (typeof(SerializationDefinition).IsAssignableFrom(serializerType)) - { - var definition = (SerializationDefinition)Activator.CreateInstance(serializerType); - config.Settings.Set("SelectedSerializer", definition); - return; - } + var settings = new SettingsHolder(); + config.Settings.SetMainSerializer(serializationDefinition, settings); + return CreateSerializationExtension(settings); + } - if (!typeof(IMessageSerializer).IsAssignableFrom(serializerType)) - { - throw new ArgumentException("The type needs to implement IMessageSerializer.", "serializerType"); - } + /// + /// Configures additional deserializers to be considered when processing messages. Can be called multiple times. + /// + /// The serializer definition eg , , etc. + /// The instance to apply the settings to. + public static SerializationExtensions AddDeserializer(this EndpointConfiguration config) where T : SerializationDefinition, new() + { + Guard.AgainstNull(nameof(config), config); + var definition = (T) Activator.CreateInstance(typeof(T)); - config.Settings.Set("SelectedSerializer", new CustomSerializer()); - config.Settings.Set("CustomSerializerType", serializerType); + return AddDeserializer(config, definition); } - internal static SerializationDefinition GetSelectedSerializer(this ReadOnlySettings settings) + /// + /// Configures additional deserializers to be considered when processing messages. Can be called multiple times. + /// + /// The serializer definition eg , , etc. + /// An instance of serialization definition. + /// The instance to apply the settings to. + public static SerializationExtensions AddDeserializer(this EndpointConfiguration config, T serializationDefinition) where T : SerializationDefinition { - SerializationDefinition selectedSerializer; - if (settings.TryGet("SelectedSerializer", out selectedSerializer)) - { - return selectedSerializer; - } + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(serializationDefinition), serializationDefinition); + + var additionalSerializers = config.GetSettings().GetAdditionalSerializers(); + + var settings = new SettingsHolder(); + additionalSerializers.Add(Tuple.Create(serializationDefinition, settings)); + return CreateSerializationExtension(settings); + } - return new XmlSerializer(); + static SerializationExtensions CreateSerializationExtension(SettingsHolder settings) where T : SerializationDefinition + { + var type = typeof(SerializationExtensions<>).MakeGenericType(typeof(T)); + var extension = (SerializationExtensions) Activator.CreateInstance(type, settings); + return extension; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationContextExtensions.cs b/src/NServiceBus.Core/Serialization/SerializationContextExtensions.cs new file mode 100644 index 00000000000..83e9da66e61 --- /dev/null +++ b/src/NServiceBus.Core/Serialization/SerializationContextExtensions.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using Pipeline; + + /// + /// Allows users to control serialization. + /// + public static class SerializationContextExtensions + { + /// + /// Requests the serializer to skip serializing the message. + /// + /// + /// This can be used by an extension point needs to take control of the serialization. + /// For example the Callbacks implementation that skips serialization and instead uses + /// headers for passing the enum or int value. + /// + public static void SkipSerialization(this IOutgoingLogicalMessageContext context) + { + context.Extensions.Set("MessageSerialization.Skip", true); + } + + /// + /// The serializer should skip serializing the message. + /// + public static bool ShouldSkipSerialization(this IOutgoingLogicalMessageContext context) + { + bool shouldSkipSerialization; + if (context.Extensions.TryGet("MessageSerialization.Skip", out shouldSkipSerialization)) + { + return shouldSkipSerialization; + } + return false; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationDefinition.cs b/src/NServiceBus.Core/Serialization/SerializationDefinition.cs index ae1c8991cf9..c46a0a61eac 100644 --- a/src/NServiceBus.Core/Serialization/SerializationDefinition.cs +++ b/src/NServiceBus.Core/Serialization/SerializationDefinition.cs @@ -1,15 +1,17 @@ namespace NServiceBus.Serialization { using System; + using MessageInterfaces; + using Settings; /// - /// Implemented by serializers to provide their capabilities + /// Implemented by serializers to provide their capabilities. /// public abstract class SerializationDefinition { /// - /// The feature to enable when this serializer is selected + /// Provides a factory method for building a message serializer. /// - protected internal abstract Type ProvidedByFeature(); + public abstract Func Configure(ReadOnlySettings settings); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationExtensions.cs b/src/NServiceBus.Core/Serialization/SerializationExtensions.cs new file mode 100644 index 00000000000..88fafb413ad --- /dev/null +++ b/src/NServiceBus.Core/Serialization/SerializationExtensions.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Serialization +{ + using Configuration.AdvanceExtensibility; + using Settings; + + /// + /// This class provides implementers of serializers with an extension mechanism for custom settings via extension methods. + /// + /// The serializer definition eg , , etc. + public class SerializationExtensions : ExposeSettings where T : SerializationDefinition + { + /// + /// Initializes a new instance of . + /// + public SerializationExtensions(SettingsHolder settings) : base(settings) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationExtentions.cs b/src/NServiceBus.Core/Serialization/SerializationExtentions.cs deleted file mode 100644 index c1efb693aeb..00000000000 --- a/src/NServiceBus.Core/Serialization/SerializationExtentions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Serialization -{ - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Settings; - - /// - /// This class provides implementers of serializers with an extension mechanism for custom settings via extention methods. - /// - /// The serializer definition eg , , etc - public class SerializationExtentions : ExposeSettings where T : SerializationDefinition - { - /// - /// Default constructor. - /// - public SerializationExtentions(SettingsHolder settings) : base(settings) - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationFeature.cs b/src/NServiceBus.Core/Serialization/SerializationFeature.cs new file mode 100644 index 00000000000..159146c539a --- /dev/null +++ b/src/NServiceBus.Core/Serialization/SerializationFeature.cs @@ -0,0 +1,81 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Features; + using Logging; + using MessageInterfaces; + using MessageInterfaces.MessageMapper.Reflection; + using Pipeline; + using Serialization; + using Settings; + using Unicast.Messages; + + class SerializationFeature : Feature + { + public SerializationFeature() + { + EnableByDefault(); + } + + protected internal sealed override void Setup(FeatureConfigurationContext context) + { + var mapper = new MessageMapper(); + var settings = context.Settings; + var messageMetadataRegistry = settings.Get(); + mapper.Initialize(messageMetadataRegistry.GetAllMessages().Select(m => m.MessageType)); + + var defaultSerializerAndDefinition = settings.GetMainSerializer(); + + var defaultSerializer = CreateMessageSerializer(defaultSerializerAndDefinition, mapper, settings); + + var additionalDeserializers = new List(); + foreach (var definitionAndSettings in context.Settings.GetAdditionalSerializers()) + { + additionalDeserializers.Add(CreateMessageSerializer(definitionAndSettings, mapper, settings)); + } + + var resolver = new MessageDeserializerResolver(defaultSerializer, additionalDeserializers); + + var logicalMessageFactory = new LogicalMessageFactory(messageMetadataRegistry, mapper); + context.Pipeline.Register(new DeserializeLogicalMessagesConnector(resolver, logicalMessageFactory, messageMetadataRegistry), "Deserializes the physical message body into logical messages"); + context.Pipeline.Register(new SerializeMessageConnector(defaultSerializer, messageMetadataRegistry), "Converts a logical message into a physical message"); + + context.Container.ConfigureComponent(_ => mapper, DependencyLifecycle.SingleInstance); + context.Container.ConfigureComponent(_ => messageMetadataRegistry, DependencyLifecycle.SingleInstance); + context.Container.ConfigureComponent(_ => logicalMessageFactory, DependencyLifecycle.SingleInstance); + + LogFoundMessages(messageMetadataRegistry.GetAllMessages().ToList()); + } + + static IMessageSerializer CreateMessageSerializer(Tuple definitionAndSettings, IMessageMapper mapper, ReadOnlySettings mainSettings) + { + var definition = definitionAndSettings.Item1; + var deserializerSettings = definitionAndSettings.Item2; + deserializerSettings.Merge(mainSettings); + deserializerSettings.PreventChanges(); + + var serializerFactory = definition.Configure(deserializerSettings); + var serializer = serializerFactory(mapper); + return serializer; + } + + static void LogFoundMessages(IReadOnlyCollection messageDefinitions) + { + if (!Logger.IsInfoEnabled) + { + return; + } + Logger.DebugFormat("Number of messages found: {0}", messageDefinitions.Count); + if (!Logger.IsDebugEnabled) + { + return; + } + Logger.DebugFormat("Message definitions: \n {0}", + string.Concat(messageDefinitions.Select(md => md + "\n"))); + } + + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationSettings.cs b/src/NServiceBus.Core/Serialization/SerializationSettings.cs deleted file mode 100644 index 536c35fdb29..00000000000 --- a/src/NServiceBus.Core/Serialization/SerializationSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Settings -{ - using System; - - /// - /// Settings related to message serialization - /// - [ObsoleteEx(RemoveInVersion = "6.0")] - public class SerializationSettings - { - - /// - /// Tells the framework to always wrap out going messages as if there was multiple messages being sent - /// - [ObsoleteEx(RemoveInVersion = "6.0", Message = "In version 5 multi-message sends was removed. So Wrapping messages is no longer required. If you are communicating with version 3 ensure you are on the latest 3.3.x.")] - public SerializationSettings WrapSingleMessages() - { - throw new NotImplementedException(); - } - - /// - /// Tells the framework to not wrap out going messages as if there was multiple messages being sent - /// - [ObsoleteEx(RemoveInVersion = "6.0", Message = "In version 5 multi-message sends was removed. So Wrapping messages is no longer required. If you are communicating with version 3 ensure you are on the latest 3.3.x.")] - public SerializationSettings DontWrapSingleMessages() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serialization/SerializationSettingsExtensions.cs b/src/NServiceBus.Core/Serialization/SerializationSettingsExtensions.cs new file mode 100644 index 00000000000..3aee3dde4da --- /dev/null +++ b/src/NServiceBus.Core/Serialization/SerializationSettingsExtensions.cs @@ -0,0 +1,49 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Serialization; + using Settings; + + static class SerializationSettingsExtensions + { + const string AdditionalSerializersSettingsKey = "AdditionalDeserializers"; + const string MainSerializerSettingsKey = "MainSerializer"; + + public static List> GetAdditionalSerializers(this SettingsHolder settings) + { + List> deserializers; + if (!settings.TryGet(AdditionalSerializersSettingsKey, out deserializers)) + { + deserializers = new List>(); + settings.Set(AdditionalSerializersSettingsKey, deserializers); + } + return deserializers; + } + + public static List> GetAdditionalSerializers(this ReadOnlySettings settings) + { + List> deserializers; + if (settings.TryGet(AdditionalSerializersSettingsKey, out deserializers)) + { + return deserializers; + } + return new List>(0); + } + + public static void SetMainSerializer(this SettingsHolder settings, SerializationDefinition definition, SettingsHolder serializerSpecificSettings) + { + settings.Set(MainSerializerSettingsKey, Tuple.Create(definition, serializerSpecificSettings)); + } + + public static Tuple GetMainSerializer(this ReadOnlySettings settings) + { + Tuple defaultSerializerAndSettings; + if (!settings.TryGet(MainSerializerSettingsKey, out defaultSerializerAndSettings)) + { + defaultSerializerAndSettings = Tuple.Create(new XmlSerializer(), new SettingsHolder()); + } + return defaultSerializerAndSettings; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Binary/Binary.cs b/src/NServiceBus.Core/Serializers/Binary/Binary.cs deleted file mode 100644 index fe76ed12d59..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/Binary.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus -{ - using System; - using Features; - using Serialization; - - /// - /// Defines the capabilities of the Binary serializer - /// - public class BinarySerializer : SerializationDefinition - { - /// - /// - /// - protected internal override Type ProvidedByFeature() - { - return typeof(BinarySerialization); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Binary/BinaryMessageSerializer.cs b/src/NServiceBus.Core/Serializers/Binary/BinaryMessageSerializer.cs deleted file mode 100644 index cef80e04955..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/BinaryMessageSerializer.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace NServiceBus.Serializers.Binary -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Runtime.Serialization; - using System.Runtime.Serialization.Formatters.Binary; - using System.Xml.Linq; - using Serialization; - - /// - /// Binary implementation of the message serializer. - /// - public class BinaryMessageSerializer : IMessageSerializer - { - /// - /// Initializes a new instance of . - /// - public BinaryMessageSerializer() - { - var surrogateSelector = new SurrogateSelector(); - surrogateSelector.AddSurrogate(typeof(XDocument), new StreamingContext(StreamingContextStates.All), new XContainerSurrogate()); - surrogateSelector.AddSurrogate(typeof(XElement), new StreamingContext(StreamingContextStates.All), new XElementSurrogate()); - - binaryFormatter.SurrogateSelector = surrogateSelector; - } - - /// - /// Serializes the given set of messages into the given stream. - /// - /// Message to serialize. - /// Stream for to be serialized into. - public void Serialize(object message, Stream stream) - { - binaryFormatter.Serialize(stream, new List { message }); - } - - /// - /// Deserializes from the given stream a set of messages. - /// - /// Stream that contains messages. - /// The list of message types to deserialize. If null the types must be inferred from the serialized data. - /// Deserialized messages. - public object[] Deserialize(Stream stream, IList messageTypes = null) - { - if (stream == null) - return null; - - var body = binaryFormatter.Deserialize(stream) as List; - - if (body == null) - return null; - - var result = new object[body.Count]; - - var i = 0; - foreach (var m in body) - result[i++] = m; - - return result; - } - - /// - /// Gets the content type into which this serializer serializes the content to - /// - public string ContentType { get{ return ContentTypes.Binary;}} - - readonly BinaryFormatter binaryFormatter = new BinaryFormatter(); - } -} diff --git a/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerialization.cs b/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerialization.cs deleted file mode 100644 index f9cc65b61ca..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerialization.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus.Features -{ - using Serializers.Binary; - - /// - /// Uses Binary as the message serialization. - /// - public class BinarySerialization : Feature - { - - internal BinarySerialization() - { - EnableByDefault(); - Prerequisite(this.ShouldSerializationFeatureBeEnabled, "BinarySerialization not enable since serialization definition not detected."); - } - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerializerConfigurationExtensions_Obsolete.cs b/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerializerConfigurationExtensions_Obsolete.cs deleted file mode 100644 index 59c8a36b8f6..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/Config/BinarySerializerConfigurationExtensions_Obsolete.cs +++ /dev/null @@ -1,24 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - using Settings; - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class BinarySerializerConfigurationExtensions - { - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure Binary(this SerializationSettings settings) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Binary/Config/ConfigureBinarySerializer_Obsolete.cs b/src/NServiceBus.Core/Serializers/Binary/Config/ConfigureBinarySerializer_Obsolete.cs deleted file mode 100644 index 6edf51d359a..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/Config/ConfigureBinarySerializer_Obsolete.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class ConfigureBinarySerializer - { - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure BinarySerializer(this Configure config) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.Core/Serializers/Binary/SimpleMessageMapper.cs b/src/NServiceBus.Core/Serializers/Binary/SimpleMessageMapper.cs deleted file mode 100644 index a6a1e8fe37c..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/SimpleMessageMapper.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Serializers.Binary -{ - using System; - using System.Collections.Generic; - using MessageInterfaces; - - /// - /// Simple implementation of message mapper for binary serialization. - /// - public class SimpleMessageMapper : IMessageMapper - { - T IMessageCreator.CreateInstance() - { - return ((IMessageCreator) this).CreateInstance(null); - } - - T IMessageCreator.CreateInstance(Action action) - { - var result = (T)((IMessageCreator)this).CreateInstance(typeof(T)); - if (action != null) - action(result); - - return result; - } - - object IMessageCreator.CreateInstance(Type messageType) - { - if (messageType.IsInterface || messageType.IsAbstract) - throw new NotSupportedException("The binary serializer does not support interface types. Please use the XML serializer if you need this functionality."); - - return Activator.CreateInstance(messageType); - } - - void IMessageMapper.Initialize(IEnumerable types) - { - } - - Type IMessageMapper.GetMappedTypeFor(Type t) - { - return t; - } - - Type IMessageMapper.GetMappedTypeFor(string typeName) - { - return Type.GetType(typeName); - } - } -} diff --git a/src/NServiceBus.Core/Serializers/Binary/XContainerSurrogate.cs b/src/NServiceBus.Core/Serializers/Binary/XContainerSurrogate.cs deleted file mode 100644 index 64a9684d2e7..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/XContainerSurrogate.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus.Serializers.Binary -{ - using System.IO; - using System.Runtime.Serialization; - using System.Xml.Linq; - - class XContainerSurrogate : ISerializationSurrogate - { - private const string FieldName = "_XDocument"; - - public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) - { - var document = (XDocument)obj; - info.AddValue(FieldName, document.ToString(SaveOptions.DisableFormatting)); - } - - public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) - { - return XDocument.Load(new StringReader(info.GetString(FieldName))); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Binary/XElementSurrogate.cs b/src/NServiceBus.Core/Serializers/Binary/XElementSurrogate.cs deleted file mode 100644 index 22cbf2fe1e1..00000000000 --- a/src/NServiceBus.Core/Serializers/Binary/XElementSurrogate.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus.Serializers.Binary -{ - using System.IO; - using System.Runtime.Serialization; - using System.Xml.Linq; - - class XElementSurrogate : ISerializationSurrogate - { - private const string FieldName = "_XElement"; - - public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) - { - var document = (XElement)obj; - info.AddValue(FieldName, document.ToString(SaveOptions.DisableFormatting)); - } - - public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) - { - return XElement.Load(new StringReader(info.GetString(FieldName))); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Bson.cs b/src/NServiceBus.Core/Serializers/Json/Bson.cs deleted file mode 100644 index 568e05ede5a..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Bson.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NServiceBus -{ - using System; - using Features; - using Serialization; - - /// - /// Defines the capabilities of the BSON serializer - /// - public class BsonSerializer : SerializationDefinition - { - /// - /// - /// - protected internal override Type ProvidedByFeature() - { - return typeof(BsonSerialization); - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/BsonMessageSerializer.cs b/src/NServiceBus.Core/Serializers/Json/BsonMessageSerializer.cs deleted file mode 100644 index f21993f4ec9..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/BsonMessageSerializer.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Serializers.Json -{ - using System; - using System.IO; - using MessageInterfaces; - using Newtonsoft.Json; - using Newtonsoft.Json.Bson; - - /// - /// BSON message serializer. - /// - public class BsonMessageSerializer : JsonMessageSerializerBase - { - /// - /// Constructor - /// - public BsonMessageSerializer(IMessageMapper messageMapper): base(messageMapper) - { - wrapMessagesInArray = true; - } - - /// - /// Creates the writer - /// - /// - protected internal override JsonWriter CreateJsonWriter(Stream stream) - { - return new BsonWriter(stream); - } - - /// - /// Creates the reader - /// - /// - protected internal override JsonReader CreateJsonReader(Stream stream) - { - return new BsonReader(stream, true, DateTimeKind.Unspecified); - } - - /// - /// Gets the supported content type - /// - protected internal override string GetContentType() - { - return ContentTypes.Bson; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Config/BsonSerialization.cs b/src/NServiceBus.Core/Serializers/Json/Config/BsonSerialization.cs deleted file mode 100644 index 474a63e5985..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Config/BsonSerialization.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NServiceBus.Features -{ - using MessageInterfaces.MessageMapper.Reflection; - using Serializers.Json; - - /// - /// Uses Bson as the message serialization. - /// - public class BsonSerialization : Feature - { - - internal BsonSerialization() - { - EnableByDefault(); - Prerequisite(this.ShouldSerializationFeatureBeEnabled, "BsonSerialization not enable since serialization definition not detected."); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Config/ConfigureJsonSerializer_Obsolete.cs b/src/NServiceBus.Core/Serializers/Json/Config/ConfigureJsonSerializer_Obsolete.cs deleted file mode 100644 index b1389c916dc..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Config/ConfigureJsonSerializer_Obsolete.cs +++ /dev/null @@ -1,31 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class ConfigureJsonSerializer - { - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure JsonSerializer(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure BsonSerializer(this Configure config) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Config/JsonSerialization.cs b/src/NServiceBus.Core/Serializers/Json/Config/JsonSerialization.cs deleted file mode 100644 index a9d6c59a123..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Config/JsonSerialization.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Features -{ - using MessageInterfaces.MessageMapper.Reflection; - using ObjectBuilder; - using Serializers.Json; - - /// - /// Uses JSON as the message serialization. - /// - public class JsonSerialization : Feature - { - internal JsonSerialization() - { - EnableByDefault(); - Prerequisite(this.ShouldSerializationFeatureBeEnabled, "JsonSerialization not enable since serialization definition not detected."); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - var c = context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - - context.Settings.ApplyTo((IComponentConfig)c); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Config/JsonSerializerConfigurationExtensions.cs b/src/NServiceBus.Core/Serializers/Json/Config/JsonSerializerConfigurationExtensions.cs index 38c2b64e97f..b05e3d5cb39 100644 --- a/src/NServiceBus.Core/Serializers/Json/Config/JsonSerializerConfigurationExtensions.cs +++ b/src/NServiceBus.Core/Serializers/Json/Config/JsonSerializerConfigurationExtensions.cs @@ -1,45 +1,23 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global + +#pragma warning disable 1591 + namespace NServiceBus { - using System; using System.Text; - using NServiceBus.Serialization; - using Serializers.Json; - using Settings; + using Serialization; - public static class JsonSerializerConfigurationExtensions + public static partial class JsonSerializerConfigurationExtensions { - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static SerializationSettings Json(this SerializationSettings settings) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static SerializationSettings Bson(this SerializationSettings settings) - { - throw new NotImplementedException(); - } - /// - /// Configures the encoding of JSON stream + /// Configures the encoding of JSON stream. /// - /// The configuration object - /// Encoding to use for serialization and deserialization - public static void Encoding(this SerializationExtentions config, Encoding encoding) + /// The configuration object. + /// Encoding to use for serialization and deserialization. + public static void Encoding(this SerializationExtensions config, Encoding encoding) { - if (encoding == null) - { - throw new ArgumentNullException("encoding"); - } - config.Settings.SetProperty(s => s.Encoding, encoding); + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(encoding), encoding); + config.Settings.Set("Serialization.Json.Encoding", encoding); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Internal/MessageContractResolver.cs b/src/NServiceBus.Core/Serializers/Json/Internal/MessageContractResolver.cs deleted file mode 100644 index ee70023db3b..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Internal/MessageContractResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Serializers.Json.Internal -{ - using System; - using MessageInterfaces; - using Newtonsoft.Json.Serialization; - - class MessageContractResolver : DefaultContractResolver - { - private readonly IMessageMapper _messageMapper; - - public MessageContractResolver(IMessageMapper messageMapper) - : base(true) - { - _messageMapper = messageMapper; - } - - protected override JsonObjectContract CreateObjectContract(Type objectType) - { - var mappedTypeFor = _messageMapper.GetMappedTypeFor(objectType); - - if (mappedTypeFor == null) - return base.CreateObjectContract(objectType); - - var jsonContract = base.CreateObjectContract(mappedTypeFor); - jsonContract.DefaultCreator = () => _messageMapper.CreateInstance(mappedTypeFor); - - return jsonContract; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Internal/MessageSerializationBinder.cs b/src/NServiceBus.Core/Serializers/Json/Internal/MessageSerializationBinder.cs deleted file mode 100644 index 479a35a30e1..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Internal/MessageSerializationBinder.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Serializers.Json.Internal -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Runtime.Serialization; - using MessageInterfaces; - - class MessageSerializationBinder : SerializationBinder - { - IMessageMapper _messageMapper; - IList messageTypes; - - public MessageSerializationBinder(IMessageMapper messageMapper, IList messageTypes = null) - { - _messageMapper = messageMapper; - this.messageTypes = messageTypes; - } - - public override void BindToName(Type serializedType, out string assemblyName, out string typeName) - { - var mappedType = _messageMapper.GetMappedTypeFor(serializedType) ?? serializedType; - - assemblyName = null; - typeName = mappedType.AssemblyQualifiedName; - } - - public override Type BindToType(string assemblyName, string typeName) - { - Type resolved = null; - if (messageTypes != null) // usually the requested message types are provided, so this should be fast - { - resolved = messageTypes.FirstOrDefault(t => t.Name.Contains(typeName)); - } - if (resolved == null) // if the type has been used before it should be resolvable like this - { - resolved = Type.GetType(typeName); - } - if (resolved == null) // if the type has not been used before, we need to find it brute force - { - resolved = AppDomain.CurrentDomain.GetAssemblies() - .Select(a => a.GetType(typeName)) - .FirstOrDefault(t => t != null); - } - return resolved; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Internal/XContainerConverter.cs b/src/NServiceBus.Core/Serializers/Json/Internal/XContainerConverter.cs deleted file mode 100644 index 2a1b047bc23..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Internal/XContainerConverter.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NServiceBus.Serializers.Json.Internal -{ - using System; - using System.Globalization; - using System.IO; - using System.Xml.Linq; - using Newtonsoft.Json; - - class XContainerConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if(value == null) - { - writer.WriteNull(); - } - - var container = (XContainer)value; - - writer.WriteValue(container.ToString(SaveOptions.DisableFormatting)); - } - - public override object ReadJson( - JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - - if (reader.TokenType != JsonToken.String) - { - throw new Exception( - string.Format( - CultureInfo.InvariantCulture, - "Unexpected token or value when parsing XContainer. Token: {0}, Value: {1}", - reader.TokenType, - reader.Value)); - } - - var value = (string)reader.Value; - if (objectType == typeof(XDocument)) - { - try - { - return XDocument.Load(new StringReader(value)); - } - catch (Exception ex) - { - throw new Exception( - string.Format( - CultureInfo.InvariantCulture, "Error parsing XContainer string: {0}", reader.Value), - ex); - } - } - - return XElement.Load(new StringReader(value)); - } - - public override bool CanConvert(Type objectType) - { - return typeof(XContainer).IsAssignableFrom(objectType); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/Json.cs b/src/NServiceBus.Core/Serializers/Json/Json.cs deleted file mode 100644 index 09eb43f009a..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/Json.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus -{ - using System; - using Features; - using Serialization; - - /// - /// Defines the capabilities of the JSON serializer - /// - public class JsonSerializer : SerializationDefinition - { - /// - /// - /// - protected internal override Type ProvidedByFeature() - { - return typeof(JsonSerialization); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializationBinder.cs b/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializationBinder.cs new file mode 100644 index 00000000000..c83be7dd5fe --- /dev/null +++ b/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializationBinder.cs @@ -0,0 +1,48 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.Serialization; + using MessageInterfaces; + + class JsonMessageSerializationBinder : SerializationBinder + { + public JsonMessageSerializationBinder(IMessageMapper messageMapper, IList messageTypes = null) + { + _messageMapper = messageMapper; + this.messageTypes = messageTypes; + } + + public override void BindToName(Type serializedType, out string assemblyName, out string typeName) + { + var mappedType = _messageMapper.GetMappedTypeFor(serializedType) ?? serializedType; + + assemblyName = null; + typeName = mappedType.AssemblyQualifiedName; + } + + public override Type BindToType(string assemblyName, string typeName) + { + Type resolved = null; + if (messageTypes != null) // usually the requested message types are provided, so this should be fast + { + resolved = messageTypes.FirstOrDefault(t => t.Name.Contains(typeName)); + } + if (resolved == null) // if the type has been used before it should be resolvable like this + { + resolved = Type.GetType(typeName); + } + if (resolved == null) // if the type has not been used before, we need to find it brute force + { + resolved = AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType(typeName)) + .FirstOrDefault(t => t != null); + } + return resolved; + } + + IMessageMapper _messageMapper; + IList messageTypes; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializer.cs b/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializer.cs index aa715f82cb8..ed4cfa51d1e 100644 --- a/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializer.cs +++ b/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializer.cs @@ -1,88 +1,220 @@ -namespace NServiceBus.Serializers.Json +namespace NServiceBus { using System; + using System.Collections.Generic; + using System.Globalization; using System.IO; + using System.Linq; + using System.Runtime.Serialization.Formatters; using System.Text; using MessageInterfaces; using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + using Serialization; - /// - /// JSON message serializer. - /// - public class JsonMessageSerializer : JsonMessageSerializerBase + class JsonMessageSerializer : IMessageSerializer { - private Encoding encoding = Encoding.UTF8; + /// + /// Initializes a new instance of . + /// + public JsonMessageSerializer(IMessageMapper messageMapper, Encoding encoding) + { + this.messageMapper = messageMapper; + this.encoding = encoding; + + messageContractResolver = new MessageContractResolver(messageMapper); + } /// - /// Constructor. + /// Initializes a new instance of . /// public JsonMessageSerializer(IMessageMapper messageMapper) - : base(messageMapper) + : this(messageMapper, Encoding.UTF8) { } + /// - /// Creates the writer + /// Gets or sets the stream encoding. /// - /// - protected internal override JsonWriter CreateJsonWriter(Stream stream) + public Encoding Encoding { - var streamWriter = new StreamWriter(stream, Encoding); - return new JsonTextWriter(streamWriter) {Formatting = Formatting.None}; + get { return encoding; } + set + { + Guard.AgainstNull(nameof(value), value); + encoding = value; + } } /// - /// Creates the reader + /// Serializes the given set of messages into the given stream. /// - /// - protected internal override JsonReader CreateJsonReader(Stream stream) + /// Message to serialize. + /// Stream for to be serialized into. + public void Serialize(object message, Stream stream) { - var streamReader = new StreamReader(stream, Encoding); - return new JsonTextReader(streamReader); + Guard.AgainstNull(nameof(stream), stream); + Guard.AgainstNull(nameof(message), message); + var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(serializerSettings); + jsonSerializer.Binder = new JsonMessageSerializationBinder(messageMapper); + + var jsonWriter = CreateJsonWriter(stream); + jsonSerializer.Serialize(jsonWriter, message); + jsonWriter.Flush(); } /// - /// Non strongly typed deserialization + /// Deserializes from the given stream a set of messages. /// - /// - /// - public object DeserializeObject(string value, Type type) + /// Stream that contains messages. + /// + /// The list of message types to deserialize. If null the types must be inferred from the + /// serialized data. + /// + /// Deserialized messages. + public object[] Deserialize(Stream stream, IList messageTypes) { - return JsonConvert.DeserializeObject(value, type); + Guard.AgainstNull(nameof(stream), stream); + var settings = serializerSettings; + + var mostConcreteType = messageTypes?.FirstOrDefault(); + var requiresDynamicDeserialization = mostConcreteType != null && mostConcreteType.IsInterface; + + if (requiresDynamicDeserialization) + { + settings = new JsonSerializerSettings + { + TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, + TypeNameHandling = TypeNameHandling.None, + Converters = + { + new IsoDateTimeConverter + { + DateTimeStyles = DateTimeStyles.RoundtripKind + }, + new XContainerJsonConverter() + } + }; + } + + var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(settings); + jsonSerializer.ContractResolver = messageContractResolver; + jsonSerializer.Binder = new JsonMessageSerializationBinder(messageMapper, messageTypes); + + var reader = CreateJsonReader(stream); + reader.Read(); + + var firstTokenType = reader.TokenType; + + if (firstTokenType == JsonToken.StartArray) + { + if (requiresDynamicDeserialization) + { + //We can safely use the first type on the list to create an array because multi-message Publish requires messages to be of same type. + return (object[]) jsonSerializer.Deserialize(reader, mostConcreteType.MakeArrayType()); + } + return jsonSerializer.Deserialize(reader); + } + if (messageTypes != null && messageTypes.Any()) + { + var rootTypes = FindRootTypes(messageTypes); + return rootTypes.Select(x => + { + if (reader == null) + { + stream.Seek(0, SeekOrigin.Begin); + reader = CreateJsonReader(stream); + reader.Read(); + } + var deserialized = jsonSerializer.Deserialize(reader, x); + reader = null; + return deserialized; + }).ToArray(); + } + return new[] + { + jsonSerializer.Deserialize(reader) + }; } /// - /// Serializes the given object to a json string + /// Gets the content type into which this serializer serializes the content to. /// - /// The actual object - /// The json string - public string SerializeObject(object value) + public string ContentType => ContentTypes.Json; + + static IEnumerable FindRootTypes(IEnumerable messageTypesToDeserialize) { - return JsonConvert.SerializeObject(value); + Type currentRoot = null; + foreach (var type in messageTypesToDeserialize) + { + if (currentRoot == null) + { + currentRoot = type; + yield return currentRoot; + continue; + } + if (!type.IsAssignableFrom(currentRoot)) + { + currentRoot = type; + yield return currentRoot; + } + } + } + + JsonWriter CreateJsonWriter(Stream stream) + { + var streamWriter = new StreamWriter(stream, Encoding); + return new JsonTextWriter(streamWriter) + { + Formatting = Formatting.None + }; + } + + JsonReader CreateJsonReader(Stream stream) + { + var streamReader = new StreamReader(stream, Encoding); + return new JsonTextReader(streamReader); } /// - /// Returns the supported content type + /// Non strongly typed deserialization. /// - protected internal override string GetContentType() + public object DeserializeObject(string value, Type type) { - return ContentTypes.Json; + Guard.AgainstNull(nameof(type), type); + Guard.AgainstNullAndEmpty(nameof(value), value); + return JsonConvert.DeserializeObject(value, type); } /// - /// Gets or sets the stream encoding + /// Serializes the given object to a json string. /// - public Encoding Encoding + /// The actual object. + /// The json string. + public string SerializeObject(object value) { - get { return encoding; } - set + Guard.AgainstNull(nameof(value), value); + return JsonConvert.SerializeObject(value); + } + + Encoding encoding; + + MessageContractResolver messageContractResolver; + IMessageMapper messageMapper; + + JsonSerializerSettings serializerSettings = new JsonSerializerSettings + { + TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, + TypeNameHandling = TypeNameHandling.Auto, + Converters = { - if (value == null) + new IsoDateTimeConverter { - throw new ArgumentNullException("value"); - } - encoding = value; + DateTimeStyles = DateTimeStyles.RoundtripKind + }, + new XContainerJsonConverter() } - } + }; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializerBase.cs b/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializerBase.cs deleted file mode 100644 index 2eab20900c9..00000000000 --- a/src/NServiceBus.Core/Serializers/Json/JsonMessageSerializerBase.cs +++ /dev/null @@ -1,187 +0,0 @@ -namespace NServiceBus.Serializers.Json -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.Serialization.Formatters; - using Internal; - using MessageInterfaces; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - using Serialization; - - /// - /// JSON and BSON base class for . - /// - public abstract class JsonMessageSerializerBase : IMessageSerializer - { - IMessageMapper messageMapper; - - JsonSerializerSettings serializerSettings = new JsonSerializerSettings - { - TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, - TypeNameHandling = TypeNameHandling.Auto, - Converters = { new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.RoundtripKind }, new XContainerConverter() } - }; - - /// - /// Ctor - /// - /// - protected internal JsonMessageSerializerBase(IMessageMapper messageMapper) - { - this.messageMapper = messageMapper; - } - - internal bool wrapMessagesInArray; - - /// - /// Removes the wrapping array if serializing a single message - /// - [ObsoleteEx( - RemoveInVersion = "6.0", - Message = "In version 5 multi-message sends was removed. So Wrapping messages is no longer required. If you are communicating with version 3 ensure you are on the latets 3.3.x.")] - public bool SkipArrayWrappingForSingleMessages { get; set; } - - /// - /// Serializes the given set of messages into the given stream. - /// - /// Message to serialize. - /// Stream for to be serialized into. - public void Serialize(object message, Stream stream) - { - var jsonSerializer = JsonSerializer.Create(serializerSettings); - jsonSerializer.Binder = new MessageSerializationBinder(messageMapper); - - var jsonWriter = CreateJsonWriter(stream); - - if (wrapMessagesInArray) - { - var objects = new[] - { - message - }; - jsonSerializer.Serialize(jsonWriter, objects); - } - else - { - jsonSerializer.Serialize(jsonWriter, message); - } - - jsonWriter.Flush(); - } - - /// - /// Deserializes from the given stream a set of messages. - /// - /// Stream that contains messages. - /// The list of message types to deserialize. If null the types must be inferred from the serialized data. - /// Deserialized messages. - public object[] Deserialize(Stream stream, IList messageTypes) - { - var settings = serializerSettings; - - var mostConcreteType = messageTypes != null ? messageTypes.FirstOrDefault() : null; - var requiresDynamicDeserialization = mostConcreteType != null && mostConcreteType.IsInterface; - - if (requiresDynamicDeserialization) - { - settings = new JsonSerializerSettings - { - TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, - TypeNameHandling = TypeNameHandling.None, - Converters = - { - new IsoDateTimeConverter - { - DateTimeStyles = DateTimeStyles.RoundtripKind - }, - new XContainerConverter() - } - }; - } - - var jsonSerializer = JsonSerializer.Create(settings); - jsonSerializer.ContractResolver = new MessageContractResolver(messageMapper); - jsonSerializer.Binder = new MessageSerializationBinder(messageMapper, messageTypes); - - var reader = CreateJsonReader(stream); - reader.Read(); - - var firstTokenType = reader.TokenType; - - if (firstTokenType == JsonToken.StartArray) - { - if (requiresDynamicDeserialization) - { - //We can safely use the first type on the list to create an array because multi-message Publish requires messages to be of same type. - return (object[]) jsonSerializer.Deserialize(reader, mostConcreteType.MakeArrayType()); - } - return jsonSerializer.Deserialize(reader); - } - if (messageTypes != null && messageTypes.Any()) - { - var rootTypes = FindRootTypes(messageTypes); - return rootTypes.Select(x => - { - if (reader == null) - { - stream.Seek(0, SeekOrigin.Begin); - reader = CreateJsonReader(stream); - reader.Read(); - } - var deserialized = jsonSerializer.Deserialize(reader, x); - reader = null; - return deserialized; - }).ToArray(); - } - return new[] - { - jsonSerializer.Deserialize(reader) - }; - } - - static IEnumerable FindRootTypes(IEnumerable messageTypesToDeserialize) - { - Type currentRoot = null; - foreach (var type in messageTypesToDeserialize) - { - if (currentRoot == null) - { - currentRoot = type; - yield return currentRoot; - continue; - } - if (!type.IsAssignableFrom(currentRoot)) - { - currentRoot = type; - yield return currentRoot; - } - } - } - - /// - /// Gets the content type into which this serializer serializes the content to - /// - public string ContentType { get { return GetContentType(); } } - - /// - /// Returns the supported content type - /// - protected internal abstract string GetContentType(); - - /// - /// Creates a from a - /// - /// The to create the for. - protected internal abstract JsonWriter CreateJsonWriter(Stream stream); - - /// - /// Creates a from a - /// - /// The to create the for. - protected internal abstract JsonReader CreateJsonReader(Stream stream); - } -} diff --git a/src/NServiceBus.Core/Serializers/Json/JsonSerializer.cs b/src/NServiceBus.Core/Serializers/Json/JsonSerializer.cs new file mode 100644 index 00000000000..18877dba218 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/Json/JsonSerializer.cs @@ -0,0 +1,23 @@ +namespace NServiceBus +{ + using System; + using System.Text; + using MessageInterfaces; + using Serialization; + using Settings; + + /// + /// Defines the capabilities of the JSON serializer. + /// + public class JsonSerializer : SerializationDefinition + { + /// + /// Provides a factory method for building a message serializer. + /// + public override Func Configure(ReadOnlySettings settings) + { + var encoding = settings.GetOrDefault("Serialization.Json.Encoding") ?? Encoding.UTF8; + return mapper => new JsonMessageSerializer(mapper, encoding); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/MessageContractResolver.cs b/src/NServiceBus.Core/Serializers/Json/MessageContractResolver.cs new file mode 100644 index 00000000000..b9c1cd81158 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/Json/MessageContractResolver.cs @@ -0,0 +1,31 @@ +namespace NServiceBus +{ + using System; + using MessageInterfaces; + using Newtonsoft.Json.Serialization; + + class MessageContractResolver : DefaultContractResolver + { + public MessageContractResolver(IMessageMapper messageMapper) + { + this.messageMapper = messageMapper; + } + + protected override JsonObjectContract CreateObjectContract(Type objectType) + { + var mappedTypeFor = messageMapper.GetMappedTypeFor(objectType); + + if (mappedTypeFor == null) + { + return base.CreateObjectContract(objectType); + } + + var jsonContract = base.CreateObjectContract(mappedTypeFor); + jsonContract.DefaultCreator = () => messageMapper.CreateInstance(mappedTypeFor); + + return jsonContract; + } + + IMessageMapper messageMapper; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/Json/XContainerJsonConverter.cs b/src/NServiceBus.Core/Serializers/Json/XContainerJsonConverter.cs new file mode 100644 index 00000000000..733a2defdd6 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/Json/XContainerJsonConverter.cs @@ -0,0 +1,65 @@ +namespace NServiceBus +{ + using System; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using Newtonsoft.Json; + + class XContainerJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + + var container = (XContainer) value; + + writer.WriteValue(container.ToString(SaveOptions.DisableFormatting)); + } + + public override object ReadJson( + JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + if (reader.TokenType != JsonToken.String) + { + throw new Exception( + string.Format( + CultureInfo.InvariantCulture, + "Unexpected token or value when parsing XContainer. Token: {0}, Value: {1}", + reader.TokenType, + reader.Value)); + } + + var value = (string) reader.Value; + if (objectType == typeof(XDocument)) + { + try + { + return XDocument.Load(new StringReader(value)); + } + catch (Exception ex) + { + throw new Exception( + string.Format( + CultureInfo.InvariantCulture, "Error parsing XContainer string: {0}", reader.Value), + ex); + } + } + + return XElement.Load(new StringReader(value)); + } + + public override bool CanConvert(Type objectType) + { + return typeof(XContainer).IsAssignableFrom(objectType); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/MessageDeserializerResolver.cs b/src/NServiceBus.Core/Serializers/MessageDeserializerResolver.cs new file mode 100644 index 00000000000..462d76b72e2 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/MessageDeserializerResolver.cs @@ -0,0 +1,41 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Serialization; + + class MessageDeserializerResolver + { + public MessageDeserializerResolver(IMessageSerializer defaultSerializer, IEnumerable additionalDeserializers) + { + this.defaultSerializer = defaultSerializer; + + foreach (var additionalDeserializer in additionalDeserializers) + { + if (serializersMap.ContainsKey(additionalDeserializer.ContentType)) + { + throw new Exception($"Multiple deserializers are registered for content-type '{additionalDeserializer.ContentType}'. Remove ambiguous deserializers."); + } + + serializersMap.Add(additionalDeserializer.ContentType, additionalDeserializer); + } + } + + public IMessageSerializer Resolve(Dictionary headers) + { + string contentType; + if (headers.TryGetValue(Headers.ContentType, out contentType)) + { + IMessageSerializer serializer; + if (serializersMap.TryGetValue(contentType, out serializer)) + { + return serializer; + } + } + return defaultSerializer; + } + + IMessageSerializer defaultSerializer; + Dictionary serializersMap = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/SerializationFeatureHelper.cs b/src/NServiceBus.Core/Serializers/SerializationFeatureHelper.cs deleted file mode 100644 index d34e667a12b..00000000000 --- a/src/NServiceBus.Core/Serializers/SerializationFeatureHelper.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus.Features -{ - /// - /// Base class for all serialization s. - /// - public static class SerializationFeatureHelper - { - /// - /// Allows serialization features to verify their Prerequisites - /// - public static bool ShouldSerializationFeatureBeEnabled(this Feature serializationFeature, FeatureConfigurationContext context) - { - var serializationDefinition = context.Settings.GetSelectedSerializer(); - return serializationDefinition.ProvidedByFeature() == serializationFeature.GetType(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/ConfigureXmlSerializer_Obsolete.cs b/src/NServiceBus.Core/Serializers/XML/Config/ConfigureXmlSerializer_Obsolete.cs deleted file mode 100644 index 0270d139608..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/ConfigureXmlSerializer_Obsolete.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static class ConfigureXmlSerializer - { - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure XmlSerializer(this Configure config, string nameSpace = null, bool sanitizeInput = false) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.Core/Serializers/XML/Config/MessageTypesInitializer.cs b/src/NServiceBus.Core/Serializers/XML/Config/MessageTypesInitializer.cs deleted file mode 100644 index eea454f3d74..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/MessageTypesInitializer.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace NServiceBus.Serializers.XML.Config -{ - using System.Linq; - using MessageInterfaces.MessageMapper.Reflection; - using NServiceBus.Config; - - /// - /// Initializes the mapper and the serializer with the found message types - /// - class MessageTypesInitializer : IWantToRunWhenConfigurationIsComplete - { - public MessageMapper Mapper { get; set; } - public XmlMessageSerializer Serializer { get; set; } - - public void Run(Configure config) - { - if (Mapper == null) - { - return; - } - - var messageTypes = config.TypesToScan.Where(config.Settings.Get().IsMessageType).ToList(); - - Mapper.Initialize(messageTypes); - - if (Serializer != null) - { - Serializer.Initialize(messageTypes); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/Xml.cs b/src/NServiceBus.Core/Serializers/XML/Config/Xml.cs deleted file mode 100644 index 52c6fd6b0d1..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/Xml.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus -{ - using System; - using Features; - using Serialization; - - /// - /// Defines the capabilities of the XML serializer - /// - public class XmlSerializer : SerializationDefinition - { - /// - /// The feature to enable when this serializer is selected - /// - protected internal override Type ProvidedByFeature() - { - return typeof(XmlSerialization); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerialization.cs b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerialization.cs deleted file mode 100644 index eb4afe303ea..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerialization.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Features -{ - using MessageInterfaces.MessageMapper.Reflection; - using ObjectBuilder; - using Serializers.XML; - - /// - /// Used to configure xml as a message serializer - /// - public class XmlSerialization : Feature - { - internal XmlSerialization() - { - EnableByDefault(); - Prerequisite(this.ShouldSerializationFeatureBeEnabled, "XmlSerialization not enable since serialization definition not detected."); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - var c = context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - - context.Settings.ApplyTo((IComponentConfig)c); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtensions.cs b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtensions.cs new file mode 100644 index 00000000000..efc6a06b9a3 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtensions.cs @@ -0,0 +1,80 @@ +namespace NServiceBus +{ + using Serialization; + + /// + /// Custom extensions for the serializer. + /// + public static partial class XmlSerializationExtensions + { + /// + /// Tells the serializer to not wrap properties which have either XDocument or XElement with a "PropertyName" element. + /// By default the xml serializer serializes the following message. + /// + /// + /// interface MyMessage { XDocument Property { get; set; } } + /// + /// into the following structure + /// + /// + /// + /// ... Content of the XDocument + /// + /// + /// + /// This flag allows to omit the property tag wrapping. Which results to + /// + /// + /// ... Content of the XDocument + /// + /// + /// When this feature is enable the root element of the XDocument must match the name of the property. The following would not work and lead to deserialization error: + /// + /// + /// + /// ... + /// + /// + /// + public static SerializationExtensions DontWrapRawXml(this SerializationExtensions config) + { + Guard.AgainstNull(nameof(config), config); + + config.Settings.Set(XmlSerializer.SkipWrappingRawXml, true); + + return config; + } + + /// + /// Configures the serializer to use a custom namespace. (http://tempuri.net) is the default. + /// If the provided namespace ends with trailing forward slashes, those will be removed on the fly. + /// + /// The to add a namespace to. + /// + /// Namespace to use for interop scenarios. + /// Note that this namespace is not validate or used for any logic inside NServiceBus. + /// It is only for scenarios where a transport (or other infrastructure) requires message xml contents to have a specific + /// namespace. + /// + public static SerializationExtensions Namespace(this SerializationExtensions config, string namespaceToUse) + { + Guard.AgainstNull(nameof(config), config); + + config.Settings.Set(XmlSerializer.CustomNamespaceConfigurationKey, namespaceToUse); + + return config; + } + + /// + /// Tells the serializer to sanitize the input data from illegal characters. + /// + public static SerializationExtensions SanitizeInput(this SerializationExtensions config) + { + Guard.AgainstNull(nameof(config), config); + + config.Settings.Set(XmlSerializer.SanitizeInput, true); + + return config; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtentions.cs b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtentions.cs deleted file mode 100644 index 16ffc867fe3..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationExtentions.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NServiceBus -{ - using System.Configuration; - using NServiceBus.Serialization; - using NServiceBus.Serializers.XML; - - /// - /// Custom extentions for the serializer. - /// - public static class XmlSerializationExtentions - { - /// - /// Tells the serializer to not wrap properties which have either XDocument or XElement with a "PropertyName" element. - /// By default the xml serializer serializes the following message - /// - /// - /// interface MyMessage { XDocument Property { get; set; } } - /// - /// into the following structure - /// - /// - /// - /// ... Content of the XDocument - /// - /// - /// - /// This flag allows to omit the property tag wrapping. Which results to - /// - /// - /// ... Content of the XDocument - /// - /// - /// When this feature is enable the root element of the XDocument must match the name of the property. The following would not work and lead to deserialization error: - /// - /// - /// - /// ... - /// - /// - /// - public static SerializationExtentions DontWrapRawXml(this SerializationExtentions config) - { - config.Settings.SetProperty(s => s.SkipWrappingRawXml, true); - - return config; - } - - /// - /// Configures the serializer to use a custom namespace. (http://tempuri.net) is the default. - /// If the provided namespace ends with trailing forward slashes, those will be removed on the fly. - /// - /// The to add a namespace to. - /// - /// Namespace to use for interop scenarios. - /// Note that this namespace is not validate or used for any logic inside NServiceBus. - /// It is only for scenarios where a transport (or other infrastructure) requires message xml contents to have a specific namespace. - /// - public static SerializationExtentions Namespace(this SerializationExtentions config, string namespaceToUse) - { - if (string.IsNullOrEmpty(namespaceToUse)) - { - throw new ConfigurationErrorsException("Can't use a null or empty string as the xml namespace"); - } - - config.Settings.SetProperty(s => s.Namespace, namespaceToUse); - - return config; - } - - /// - /// Tells the serializer to sanitize the input data from illegal characters - /// - public static SerializationExtentions SanitizeInput(this SerializationExtentions config) - { - config.Settings.SetProperty(s => s.SanitizeInput, true); - - return config; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationSettings_Obsolete.cs b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationSettings_Obsolete.cs deleted file mode 100644 index 1c01c294087..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializationSettings_Obsolete.cs +++ /dev/null @@ -1,41 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus.Serializers.XML.Config -{ - using System; - - [ObsoleteEx( - Message = "Use configuration.UseSerialization(), where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public class XmlSerializationSettings - { - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization().DontWrapRawXml()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public XmlSerializationSettings DontWrapRawXml() - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization().Namespace(namespaceToUse)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public XmlSerializationSettings Namespace(string namespaceToUse) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseSerialization().SanitizeInput()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public XmlSerializationSettings SanitizeInput() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializerConfigurationExtensions_Obsolete.cs b/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializerConfigurationExtensions_Obsolete.cs deleted file mode 100644 index ff7f2b0aea7..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Config/XmlSerializerConfigurationExtensions_Obsolete.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using Serializers.XML.Config; - using Settings; - - /// - /// XmlSerializer configuration extensions. - /// - public static class XmlSerializerConfigurationExtensions - { - /// - /// Enables the xml message serializer with the given settings - /// - [ObsoleteEx( - Message = "Use configuration.UseSerialization(), where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure Xml(this SerializationSettings settings, Action customSettings = null) - { - throw new NotImplementedException(); - } - - } -} diff --git a/src/NServiceBus.Core/Serializers/XML/Deserializer.cs b/src/NServiceBus.Core/Serializers/XML/Deserializer.cs deleted file mode 100644 index 17285bf3e5d..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Deserializer.cs +++ /dev/null @@ -1,637 +0,0 @@ -namespace NServiceBus.Serializers.XML -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.Serialization; - using System.Xml; - using System.Xml.Linq; - using NServiceBus.Logging; - using NServiceBus.MessageInterfaces; - using NServiceBus.Utils.Reflection; - - class Deserializer - { - public Deserializer(IMessageMapper mapper, XmlSerializerCache cache) - { - this.mapper = mapper; - this.cache = cache; - } - - public bool SkipWrappingRawXml { get; set; } - - public bool SanitizeInput { get; set; } - - public object[] Deserialize(Stream stream, IList messageTypesToDeserialize = null) - { - if (stream == null) - { - return null; - } - - var result = new List(); - - var doc = new XmlDocument - { - PreserveWhitespace = true - }; - - var reader = SanitizeInput - ? XmlReader.Create(new XmlSanitizingStream(stream), new XmlReaderSettings - { - CheckCharacters = false - }) - : XmlReader.Create(stream, new XmlReaderSettings - { - CheckCharacters = false - }); - - doc.Load(reader); - - if (doc.DocumentElement == null) - { - return result.ToArray(); - } - - foreach (XmlAttribute attr in doc.DocumentElement.Attributes) - { - if (attr.Name == "xmlns") - { - defaultNameSpace = attr.Value.Substring(attr.Value.LastIndexOf("/") + 1); - } - else - { - if (attr.Name.Contains("xmlns:")) - { - var colonIndex = attr.Name.LastIndexOf(":"); - var prefix = attr.Name.Substring(colonIndex + 1); - - if (prefix.Contains(BASETYPE)) - { - var baseType = mapper.GetMappedTypeFor(attr.Value); - if (baseType != null) - { - messageBaseTypes.Add(baseType); - } - } - else - { - prefixesToNamespaces[prefix] = attr.Value; - } - } - } - } - - if (doc.DocumentElement.Name.ToLower() != "messages") - { - if (messageTypesToDeserialize != null && messageTypesToDeserialize.Any()) - { - var rootTypes = FindRootTypes(messageTypesToDeserialize); - foreach (var rootType in rootTypes) - { - var m = Process(doc.DocumentElement, null, rootType); - if (m == null) - { - throw new SerializationException("Could not deserialize message."); - } - result.Add(m); - } - } - else - { - var m = Process(doc.DocumentElement, null); - if (m == null) - { - throw new SerializationException("Could not deserialize message."); - } - result.Add(m); - } - } - else - { - var position = 0; - foreach (XmlNode node in doc.DocumentElement.ChildNodes) - { - if (node.NodeType == XmlNodeType.Whitespace) - { - continue; - } - - - Type nodeType = null; - - if (messageTypesToDeserialize != null && position < messageTypesToDeserialize.Count) - { - nodeType = messageTypesToDeserialize.ElementAt(position); - } - - - var m = Process(node, null, nodeType); - - result.Add(m); - - position++; - } - } - - return result.ToArray(); - } - - static IEnumerable FindRootTypes(IEnumerable messageTypesToDeserialize) - { - Type currentRoot = null; - foreach (var type in messageTypesToDeserialize) - { - if (currentRoot == null) - { - currentRoot = type; - yield return currentRoot; - continue; - } - if (!type.IsAssignableFrom(currentRoot)) - { - currentRoot = type; - yield return currentRoot; - } - } - } - - object Process(XmlNode node, object parent, Type nodeType = null) - { - if (nodeType == null) - { - nodeType = InferNodeType(node, parent); - } - - return GetObjectOfTypeFromNode(nodeType, node); - } - - Type InferNodeType(XmlNode node, object parent) - { - var name = node.Name; - var typeName = name; - - if (!string.IsNullOrEmpty(defaultNameSpace)) - { - typeName = defaultNameSpace + "." + typeName; - } - - if (name.Contains(":")) - { - var colonIndex = node.Name.IndexOf(":"); - name = name.Substring(colonIndex + 1); - var prefix = node.Name.Substring(0, colonIndex); - var ns = prefixesToNamespaces[prefix]; - - typeName = ns.Substring(ns.LastIndexOf("/") + 1) + "." + name; - } - - if (name.Contains("NServiceBus.")) - { - typeName = name; - } - - - if (parent != null) - { - if (parent is IEnumerable) - { - if (parent.GetType().IsArray) - { - return parent.GetType().GetElementType(); - } - - var listImplementations = parent.GetType().GetInterfaces().Where(interfaceType => interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IList<>)).ToList(); - if (listImplementations.Any()) - { - var listImplementation = listImplementations.First(); - return listImplementation.GetGenericArguments().Single(); - } - - var args = parent.GetType().GetGenericArguments(); - - if (args.Length == 1) - { - return args[0]; - } - } - - var prop = parent.GetType().GetProperty(name); - - if (prop != null) - { - return prop.PropertyType; - } - } - - var mappedType = mapper.GetMappedTypeFor(typeName); - - if (mappedType != null) - { - return mappedType; - } - - logger.Debug("Could not load " + typeName + ". Trying base types..."); - foreach (var baseType in messageBaseTypes) - { - try - { - logger.Debug("Trying to deserialize message to " + baseType.FullName); - return baseType; - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - // intentionally swallow exception - } - } - - throw new Exception("Could not determine type for node: '" + node.Name + "'."); - } - - object GetObjectOfTypeFromNode(Type t, XmlNode node) - { - if (t.IsSimpleType() || t == typeof(Uri) || t.IsNullableType()) - { - return GetPropertyValue(t, node); - } - - if (typeof(IEnumerable).IsAssignableFrom(t)) - { - return GetPropertyValue(t, node); - } - - var result = mapper.CreateInstance(t); - - foreach (XmlNode n in node.ChildNodes) - { - Type type = null; - if (n.Name.Contains(":")) - { - type = Type.GetType("System." + n.Name.Substring(0, n.Name.IndexOf(":")), false, true); - } - - var prop = GetProperty(t, n.Name); - if (prop != null) - { - var val = GetPropertyValue(type ?? prop.PropertyType, n); - if (val != null) - { - var propertySet = DelegateFactory.CreateSet(prop); - propertySet.Invoke(result, val); - continue; - } - } - - var field = GetField(t, n.Name); - if (field != null) - { - var val = GetPropertyValue(type ?? field.FieldType, n); - if (val != null) - { - var fieldSet = DelegateFactory.CreateSet(field); - fieldSet.Invoke(result, val); - } - } - } - - return result; - } - - FieldInfo GetField(Type t, string name) - { - IEnumerable fields; - cache.typeToFields.TryGetValue(t, out fields); - - if (fields == null) - { - return null; - } - - foreach (var f in fields) - { - if (f.Name == name) - { - return f; - } - } - - return null; - } - - object GetPropertyValue(Type type, XmlNode n) - { - if ((n.ChildNodes.Count == 1) && (n.ChildNodes[0] is XmlCharacterData)) - { - var text = n.ChildNodes[0].InnerText; - - var args = type.GetGenericArguments(); - if (args.Length == 1 && args[0].IsValueType) - { - if (args[0].GetGenericArguments().Any()) - { - return GetPropertyValue(args[0], n); - } - - var nullableType = typeof(Nullable<>).MakeGenericType(args); - if (type == nullableType) - { - if (text.Trim().ToLower() == "null") - { - return null; - } - - return GetPropertyValue(args[0], n); - } - } - - if (type == typeof(string)) - { - return text; - } - - if (type == typeof(Boolean)) - { - return XmlConvert.ToBoolean(text); - } - - if (type == typeof(Byte)) - { - return XmlConvert.ToByte(text); - } - - if (type == typeof(Char)) - { - return XmlConvert.ToChar(text); - } - - if (type == typeof(DateTime)) - { - return XmlConvert.ToDateTime(text, XmlDateTimeSerializationMode.RoundtripKind); - } - - if (type == typeof(DateTimeOffset)) - { - return XmlConvert.ToDateTimeOffset(text); - } - - if (type == typeof(decimal)) - { - return XmlConvert.ToDecimal(text); - } - - if (type == typeof(double)) - { - return XmlConvert.ToDouble(text); - } - - if (type == typeof(Guid)) - { - return XmlConvert.ToGuid(text); - } - - if (type == typeof(Int16)) - { - return XmlConvert.ToInt16(text); - } - - if (type == typeof(Int32)) - { - return XmlConvert.ToInt32(text); - } - - if (type == typeof(Int64)) - { - return XmlConvert.ToInt64(text); - } - - if (type == typeof(sbyte)) - { - return XmlConvert.ToSByte(text); - } - - if (type == typeof(Single)) - { - return XmlConvert.ToSingle(text); - } - - if (type == typeof(TimeSpan)) - { - return XmlConvert.ToTimeSpan(text); - } - - if (type == typeof(UInt16)) - { - return XmlConvert.ToUInt16(text); - } - - if (type == typeof(UInt32)) - { - return XmlConvert.ToUInt32(text); - } - - if (type == typeof(UInt64)) - { - return XmlConvert.ToUInt64(text); - } - - if (type.IsEnum) - { - return Enum.Parse(type, text); - } - - if (type == typeof(byte[])) - { - return Convert.FromBase64String(text); - } - - if (type == typeof(Uri)) - { - return new Uri(text); - } - - if (!typeof(IEnumerable).IsAssignableFrom(type)) - { - if (n.ChildNodes[0] is XmlWhitespace) - { - return Activator.CreateInstance(type); - } - - throw new Exception("Type not supported by the serializer: " + type.AssemblyQualifiedName); - } - } - - if (typeof(XContainer).IsAssignableFrom(type)) - { - var reader = new StringReader(SkipWrappingRawXml ? n.OuterXml : n.InnerXml); - - if (type == typeof(XDocument)) - { - return XDocument.Load(reader); - } - - return XElement.Load(reader); - } - - //Handle dictionaries - if (typeof(IDictionary).IsAssignableFrom(type)) - { - var result = Activator.CreateInstance(type) as IDictionary; - - var keyType = typeof(object); - var valueType = typeof(object); - - foreach (var interfaceType in type.GetInterfaces()) - { - var args = interfaceType.GetGenericArguments(); - if (args.Length != 2) - { - continue; - } - - if (typeof(IDictionary<,>).MakeGenericType(args[0], args[1]).IsAssignableFrom(type)) - { - keyType = args[0]; - valueType = args[1]; - break; - } - } - - foreach (XmlNode xn in n.ChildNodes) // go over KeyValuePairs - { - object key = null; - object value = null; - - foreach (XmlNode node in xn.ChildNodes) - { - if (node.Name == "Key") - { - key = GetObjectOfTypeFromNode(keyType, node); - } - if (node.Name == "Value") - { - value = GetObjectOfTypeFromNode(valueType, node); - } - } - - if (result != null && key != null) - { - result[key] = value; - } - } - - return result; - } - - if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string)) - { - var isArray = type.IsArray; - - var isISet = false; - if (type.IsGenericType && type.GetGenericArguments().Length == 1) - { - var setType = typeof(ISet<>).MakeGenericType(type.GetGenericArguments()); - isISet = setType.IsAssignableFrom(type); - } - - var typeToCreate = type; - if (isArray) - { - typeToCreate = cache.typesToCreateForArrays[type]; - } - - Type typeToCreateForEnumerables; - if (cache.typesToCreateForEnumerables.TryGetValue(type, out typeToCreateForEnumerables)) //handle IEnumerable - { - typeToCreate = typeToCreateForEnumerables; - } - - if (typeof(IList).IsAssignableFrom(typeToCreate)) - { - var list = Activator.CreateInstance(typeToCreate) as IList; - if (list != null) - { - foreach (XmlNode xn in n.ChildNodes) - { - if (xn.NodeType == XmlNodeType.Whitespace) - { - continue; - } - - var m = Process(xn, list); - list.Add(m); - } - - if (isArray) - { - return typeToCreate.GetMethod("ToArray").Invoke(list, null); - } - - if (isISet) - { - return Activator.CreateInstance(type, typeToCreate.GetMethod("ToArray").Invoke(list, null)); - } - } - - return list; - } - } - - if (n.ChildNodes.Count == 0) - { - if (type == typeof(string)) - { - return string.Empty; - } - return null; - } - - return GetObjectOfTypeFromNode(type, n); - } - - PropertyInfo GetProperty(Type t, string name) - { - IEnumerable props; - cache.typeToProperties.TryGetValue(t, out props); - - if (props == null) - { - return null; - } - - var n = GetNameAfterColon(name); - - foreach (var prop in props) - { - if (prop.Name == n) - { - return prop; - } - } - - return null; - } - - static string GetNameAfterColon(string name) - { - var n = name; - if (name.Contains(":")) - { - n = name.Substring(name.IndexOf(":") + 1, name.Length - name.IndexOf(":") - 1); - } - - return n; - } - - const string BASETYPE = "baseType"; - static ILog logger = LogManager.GetLogger(); - readonly XmlSerializerCache cache; - readonly IMessageMapper mapper; - string defaultNameSpace; - List messageBaseTypes = new List(); - IDictionary prefixesToNamespaces = new Dictionary(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/Serializer.cs b/src/NServiceBus.Core/Serializers/XML/Serializer.cs deleted file mode 100644 index 9bcb1dfae62..00000000000 --- a/src/NServiceBus.Core/Serializers/XML/Serializer.cs +++ /dev/null @@ -1,548 +0,0 @@ -namespace NServiceBus.Serializers.XML -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Text; - using System.Xml; - using System.Xml.Linq; - using NServiceBus.MessageInterfaces; - using NServiceBus.Utils.Reflection; - - class Serializer - { - const string BASETYPE = "baseType"; - - string nameSpace = "http://tempuri.net"; - List namespacesToAdd = new List(); - - readonly IMessageMapper mapper; - readonly Conventions conventions; - readonly XmlSerializerCache cache; - - public Serializer(IMessageMapper mapper, Conventions conventions, XmlSerializerCache cache) - { - this.mapper = mapper; - this.conventions = conventions; - this.cache = cache; - } - - public bool SkipWrappingRawXml { get; set; } - - public string Namespace - { - get { return nameSpace; } - set { nameSpace = TrimPotentialTrailingForwardSlashes(value); } - } - - public byte[] Serialize(object message) - { - var serializedMessage = SerializeMessage(message); - - return Encoding.UTF8.GetBytes(serializedMessage); - } - - string InitializeNamespace(object message) - { - namespacesToAdd = new List(); - - return GetNamespace(message); - } - - string GetNamespace(object message) - { - //TODO: if the proxy type has the same NS as the real message type we don't need to look this up - return mapper.GetMappedTypeFor(message.GetType()).Namespace; - } - - string SerializeMessage(object message) - { - var messageBuilder = new StringBuilder(); - - messageBuilder.AppendLine(""); - - var t = mapper.GetMappedTypeFor(message.GetType()); - var baseTypes = GetBaseTypes(message); - - var objectBuilder = new StringBuilder(); - var elementName = t.SerializationFriendlyName(); - - WriteObject(elementName, t, message, objectBuilder); - - var startElementBuilder = new StringBuilder(); - var ns = GetNamespace(message); - WriteElementWithNamespaces(ns, baseTypes, startElementBuilder, elementName); - - objectBuilder.Replace(string.Format("<{0}>", elementName), startElementBuilder.ToString()); - messageBuilder.AppendLine(objectBuilder.ToString().TrimEnd()); - - return messageBuilder.ToString(); - } - - static string FormatAsString(object value) - { - if (value == null) - { - return "null"; - } - if (value is bool) - { - return XmlConvert.ToString((bool)value); - } - if (value is byte) - { - return XmlConvert.ToString((byte)value); - } - if (value is char) - { - return Escape((char)value); - } - if (value is double) - { - return XmlConvert.ToString((double)value); - } - if (value is ulong) - { - return XmlConvert.ToString((ulong)value); - } - if (value is uint) - { - return XmlConvert.ToString((uint)value); - } - if (value is ushort) - { - return XmlConvert.ToString((ushort)value); - } - if (value is long) - { - return XmlConvert.ToString((long)value); - } - if (value is int) - { - return XmlConvert.ToString((int)value); - } - if (value is short) - { - return XmlConvert.ToString((short)value); - } - if (value is sbyte) - { - return XmlConvert.ToString((sbyte)value); - } - if (value is decimal) - { - return XmlConvert.ToString((decimal)value); - } - if (value is float) - { - return XmlConvert.ToString((float)value); - } - if (value is Guid) - { - return XmlConvert.ToString((Guid)value); - } - if (value is DateTime) - { - return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.RoundtripKind); - } - if (value is DateTimeOffset) - { - return XmlConvert.ToString((DateTimeOffset)value); - } - if (value is TimeSpan) - { - return XmlConvert.ToString((TimeSpan)value); - } - if (value is string) - { - return Escape((string)value); - } - - return Escape(value.ToString()); - } - -#pragma warning disable 652 - - static string Escape(char c) - { - if (c == 0x9 || c == 0xA || c == 0xD - || (0x20 <= c && c <= 0xD7FF) - || (0xE000 <= c && c <= 0xFFFD) - || (0x10000 <= c && c <= 0x10ffff) - ) - { - string ss = null; - switch (c) - { - case '<': - ss = "<"; - break; - - case '>': - ss = ">"; - break; - - case '"': - ss = """; - break; - - case '\'': - ss = "'"; - break; - - case '&': - ss = "&"; - break; - } - if (ss != null) - { - return ss; - } - } - else - { - return String.Format("&#x{0:X};", (int)c); - } - - //Should not get here but just in case! - return c.ToString(); - } - - static string Escape(string stringToEscape) - { - if (string.IsNullOrEmpty(stringToEscape)) - { - return stringToEscape; - } - - StringBuilder builder = null; // initialize if we need it - - var startIndex = 0; - for (var i = 0; i < stringToEscape.Length; ++i) - { - var c = stringToEscape[i]; - if (c == 0x9 || c == 0xA || c == 0xD - || (0x20 <= c && c <= 0xD7FF) - || (0xE000 <= c && c <= 0xFFFD) - || (0x10000 <= c && c <= 0x10ffff) - ) - { - string ss = null; - switch (c) - { - case '<': - ss = "<"; - break; - - case '>': - ss = ">"; - break; - - case '"': - ss = """; - break; - - case '\'': - ss = "'"; - break; - - case '&': - ss = "&"; - break; - } - if (ss != null) - { - if (builder == null) - { - builder = new StringBuilder(stringToEscape.Length + ss.Length); - } - if (startIndex < i) - { - builder.Append(stringToEscape, startIndex, i - startIndex); - } - startIndex = i + 1; - builder.Append(ss); - } - } - else - { - // invalid characters - if (builder == null) - { - builder = new StringBuilder(stringToEscape.Length + 8); - } - if (startIndex < i) - { - builder.Append(stringToEscape, startIndex, i - startIndex); - } - startIndex = i + 1; - builder.AppendFormat("&#x{0:X};", (int)c); - } - } - - if (startIndex < stringToEscape.Length) - { - if (builder == null) - { - return stringToEscape; - } - builder.Append(stringToEscape, startIndex, stringToEscape.Length - startIndex); - } - - if (builder != null) - { - return builder.ToString(); - } - - //Should not get here but just in case! - return stringToEscape; - } -#pragma warning restore 652 - - List GetBaseTypes(object message) - { - var result = new List(); - - var t = mapper.GetMappedTypeFor(message.GetType()); - - var baseType = t.BaseType; - while (baseType != typeof(object) && baseType != null) - { - if (conventions.IsMessageType(baseType)) - { - if (!result.Contains(baseType.FullName)) - { - result.Add(baseType.FullName); - } - } - - baseType = baseType.BaseType; - } - - foreach (var i in t.GetInterfaces()) - { - if (conventions.IsMessageType(i)) - { - if (!result.Contains(i.FullName)) - { - result.Add(i.FullName); - } - } - } - - return result; - } - - void WriteObject(string name, Type type, object value, StringBuilder builder) - { - var element = name; - - if (type == typeof(object) && value.GetType().IsSimpleType()) - { - if (!namespacesToAdd.Contains(value.GetType())) - { - namespacesToAdd.Add(value.GetType()); - } - - builder.AppendFormat("<{0}>{1}\n", - value.GetType().Name.ToLower() + ":" + name, - FormatAsString(value)); - - return; - } - - builder.AppendFormat("<{0}>\n", element); - - Write(builder, type, value); - - builder.AppendFormat("\n", element); - } - - void Write(StringBuilder builder, Type t, object obj) - { - if (obj == null) - { - // For null entries in a nullable array - // See https://github.com/Particular/NServiceBus/issues/2706 - if (t.IsNullableType()) - builder.Append("null"); - - return; - } - - IEnumerable properties; - if (!cache.typeToProperties.TryGetValue(t, out properties)) - { - throw new InvalidOperationException(string.Format("Type {0} was not registered in the serializer. Check that it appears in the list of configured assemblies/types to scan.", t.FullName)); - } - - foreach (var prop in properties) - { - if (IsIndexedProperty(prop)) - { - throw new NotSupportedException(string.Format("Type {0} contains an indexed property named {1}. Indexed properties are not supported on message types.", t.FullName, prop.Name)); - } - WriteEntry(prop.Name, prop.PropertyType, DelegateFactory.CreateGet(prop).Invoke(obj), builder); - } - - foreach (var field in cache.typeToFields[t]) - { - WriteEntry(field.Name, field.FieldType, DelegateFactory.CreateGet(field).Invoke(obj), builder); - } - } - - void WriteEntry(string name, Type type, object value, StringBuilder builder) - { - if (value == null) - { - if (typeof(IEnumerable).IsAssignableFrom(type)) - { - return; - } - - var args = type.GetGenericArguments(); - if (args.Length == 1 && args[0].IsValueType) - { - var nullableType = typeof(Nullable<>).MakeGenericType(args); - if (type == nullableType) - { - WriteEntry(name, typeof(string), "null", builder); - return; - } - } - - return; - } - - if (typeof(XContainer).IsAssignableFrom(type)) - { - var container = (XContainer)value; - if (SkipWrappingRawXml) - { - builder.AppendFormat("{0}\n", container); - } - else - { - builder.AppendFormat("<{0}>{1}\n", name, container); - } - - return; - } - - if (type.IsValueType || type == typeof(string) || type == typeof(Uri) || type == typeof(char)) - { - builder.AppendFormat("<{0}>{1}\n", name, FormatAsString(value)); - return; - } - - if (typeof(IEnumerable).IsAssignableFrom(type)) - { - builder.AppendFormat("<{0}>\n", name); - - if (type == typeof(byte[])) - { - var base64String = Convert.ToBase64String((byte[])value); - builder.Append(base64String); - } - else - { - var baseType = typeof(object); - - var interfaces = type.GetInterfaces(); - if (type.IsInterface) - { - interfaces = interfaces.Union(new[] - { - type - }).ToArray(); - } - - //Get generic type from list: T for List, KeyValuePair for IDictionary - foreach (var interfaceType in interfaces) - { - var arr = interfaceType.GetGenericArguments(); - if (arr.Length != 1) - { - continue; - } - - if (typeof(IEnumerable<>).MakeGenericType(arr[0]).IsAssignableFrom(type)) - { - baseType = arr[0]; - break; - } - } - - foreach (var obj in ((IEnumerable)value)) - { - if (obj != null && obj.GetType().IsSimpleType()) - { - WriteEntry(obj.GetType().Name, obj.GetType(), obj, builder); - } - else - { - WriteObject(baseType.SerializationFriendlyName(), baseType, obj, builder); - } - } - } - - builder.AppendFormat("\n", name); - return; - } - - WriteObject(name, type, value, builder); - } - - static bool IsIndexedProperty(PropertyInfo propertyInfo) - { - if (propertyInfo != null) - { - return propertyInfo.GetIndexParameters().Length > 0; - } - - return false; - } - - string TrimPotentialTrailingForwardSlashes(string value) - { - if (value == null) - { - return null; - } - - return value.TrimEnd(new[] - { - '/' - }); - } - - void WriteElementWithNamespaces(string messageNamespace, List baseTypes, StringBuilder builder, string element) - { - builder.AppendFormat( - "<{0} xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", - element); - - builder.AppendFormat(" xmlns=\"{0}/{1}\"", nameSpace, messageNamespace); - - foreach (var t in namespacesToAdd) - { - builder.AppendFormat(" xmlns:{0}=\"{1}\"", t.Name.ToLower(), t.Name); - } - - for (var i = 0; i < baseTypes.Count; i++) - { - var prefix = BASETYPE; - if (i != 0) - { - prefix += i; - } - - builder.AppendFormat(" xmlns:{0}=\"{1}\"", prefix, baseTypes[i]); - } - - builder.Append(">"); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlDeserialization.cs b/src/NServiceBus.Core/Serializers/XML/XmlDeserialization.cs new file mode 100644 index 00000000000..2a7feedb861 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/XML/XmlDeserialization.cs @@ -0,0 +1,679 @@ +namespace NServiceBus +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + using System.Xml; + using System.Xml.Linq; + using Logging; + using MessageInterfaces; + + class XmlDeserialization + { + public XmlDeserialization(IMessageMapper mapper, XmlSerializerCache cache, bool skipWrappingRawXml, bool sanitizeInput) + { + this.mapper = mapper; + this.cache = cache; + this.skipWrappingRawXml = skipWrappingRawXml; + this.sanitizeInput = sanitizeInput; + } + + public object[] Deserialize(Stream stream, IList messageTypesToDeserialize = null) + { + if (stream == null) + { + return null; + } + + var result = new List(); + + var doc = ReadStreamIntoDocument(stream, sanitizeInput); + + if (NothingToBeProcessed(doc)) + { + return result.ToArray(); + } + + CacheDefaultNameSpaceMessageBaseTypesAndPrefixes(doc); + + if (ContainsMultipleMessages(doc)) + { + if (ContainsAnyMessageTypesToDeserialize(messageTypesToDeserialize)) + { + var rootTypes = FindRootTypes(messageTypesToDeserialize); + ProcessRootTypes(rootTypes, doc, result); + } + else + { + var m = Process(doc.DocumentElement, null); + if (m == null) + { + throw new SerializationException("Could not deserialize message."); + } + result.Add(m); + } + } + else + { + ProcessChildNodes(messageTypesToDeserialize, doc, result); + } + + return result.ToArray(); + } + + static bool NothingToBeProcessed(XmlDocument doc) + { + return doc.DocumentElement == null; + } + + static XmlDocument ReadStreamIntoDocument(Stream stream, bool sanitizeInput) + { + var doc = new XmlDocument + { + PreserveWhitespace = true + }; + + var reader = sanitizeInput + ? XmlReader.Create(new XmlSanitizingStream(stream), new XmlReaderSettings + { + CheckCharacters = false + }) + : XmlReader.Create(stream, new XmlReaderSettings + { + CheckCharacters = false + }); + + doc.Load(reader); + return doc; + } + + void ProcessRootTypes(IEnumerable rootTypes, XmlDocument doc, ICollection result) + { + foreach (var rootType in rootTypes) + { + var m = Process(doc.DocumentElement, null, rootType); + if (m == null) + { + throw new SerializationException("Could not deserialize message."); + } + result.Add(m); + } + } + + static bool ContainsAnyMessageTypesToDeserialize(IList messageTypesToDeserialize) + { + return messageTypesToDeserialize != null && messageTypesToDeserialize.Any(); + } + + void ProcessChildNodes(IList messageTypesToDeserialize, XmlDocument doc, ICollection result) + { + var position = 0; + foreach (XmlNode node in doc.DocumentElement.ChildNodes) + { + if (node.NodeType == XmlNodeType.Whitespace) + { + continue; + } + + var nodeType = ExtractNodeTypeAtPosition(messageTypesToDeserialize, position); + + var m = Process(node, null, nodeType); + + result.Add(m); + + position++; + } + } + + static Type ExtractNodeTypeAtPosition(IList messageTypesToDeserialize, int position) + { + Type nodeType = null; + if (messageTypesToDeserialize != null && position < messageTypesToDeserialize.Count) + { + nodeType = messageTypesToDeserialize.ElementAt(position); + } + return nodeType; + } + + void CacheDefaultNameSpaceMessageBaseTypesAndPrefixes(XmlDocument doc) + { + foreach (XmlAttribute attr in doc.DocumentElement.Attributes) + { + if (attr.Name == "xmlns") + { + defaultNameSpace = attr.Value.Substring(attr.Value.LastIndexOf("/") + 1); + } + else + { + if (attr.Name.Contains("xmlns:")) + { + var colonIndex = attr.Name.LastIndexOf(":"); + var prefix = attr.Name.Substring(colonIndex + 1); + + if (prefix.Contains(BASETYPE)) + { + var baseType = mapper.GetMappedTypeFor(attr.Value); + if (baseType != null) + { + messageBaseTypes.Add(baseType); + } + } + else + { + prefixesToNamespaces[prefix] = attr.Value; + } + } + } + } + } + + static bool ContainsMultipleMessages(XmlDocument doc) + { + return doc.DocumentElement.Name.ToLower() != "messages"; + } + + static IEnumerable FindRootTypes(IEnumerable messageTypesToDeserialize) + { + Type currentRoot = null; + foreach (var type in messageTypesToDeserialize) + { + if (currentRoot == null) + { + currentRoot = type; + yield return currentRoot; + continue; + } + if (!type.IsAssignableFrom(currentRoot)) + { + currentRoot = type; + yield return currentRoot; + } + } + } + + object Process(XmlNode node, object parent, Type nodeType = null) + { + if (nodeType == null) + { + nodeType = InferNodeType(node, parent); + } + + return GetObjectOfTypeFromNode(nodeType, node); + } + + Type InferNodeType(XmlNode node, object parent) + { + var name = node.Name; + var typeName = name; + + if (!string.IsNullOrEmpty(defaultNameSpace)) + { + typeName = $"{defaultNameSpace}.{typeName}"; + } + + if (name.Contains(":")) + { + var colonIndex = node.Name.IndexOf(":"); + name = name.Substring(colonIndex + 1); + var prefix = node.Name.Substring(0, colonIndex); + var ns = prefixesToNamespaces[prefix]; + + typeName = $"{ns.Substring(ns.LastIndexOf("/") + 1)}.{name}"; + } + + if (name.Contains("NServiceBus.")) + { + typeName = name; + } + + if (parent != null) + { + if (parent is IEnumerable) + { + if (parent.GetType().IsArray) + { + return parent.GetType().GetElementType(); + } + + var listImplementations = parent.GetType().GetInterfaces().Where(interfaceType => interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IList<>)).ToList(); + if (listImplementations.Any()) + { + var listImplementation = listImplementations.First(); + return listImplementation.GetGenericArguments().Single(); + } + + var args = parent.GetType().GetGenericArguments(); + + if (args.Length == 1) + { + return args[0]; + } + } + + var prop = parent.GetType().GetProperty(name); + + if (prop != null) + { + return prop.PropertyType; + } + } + + var mappedType = mapper.GetMappedTypeFor(typeName); + + if (mappedType != null) + { + return mappedType; + } + + logger.Debug($"Could not load {typeName}. Trying base types..."); + foreach (var baseType in messageBaseTypes) + { + try + { + logger.Debug($"Trying to deserialize message to {baseType.FullName}"); + return baseType; + } + // ReSharper disable once EmptyGeneralCatchClause + catch + { + // intentionally swallow exception + } + } + + throw new Exception($"Could not determine type for node: '{node.Name}'."); + } + + object GetObjectOfTypeFromNode(Type t, XmlNode node) + { + if (t.IsSimpleType() || t == typeof(Uri) || t.IsNullableType()) + { + return GetPropertyValue(t, node); + } + + if (typeof(IEnumerable).IsAssignableFrom(t)) + { + return GetPropertyValue(t, node); + } + + var result = mapper.CreateInstance(t); + + foreach (XmlNode n in node.ChildNodes) + { + Type type = null; + if (n.Name.Contains(":")) + { + type = Type.GetType($"System.{n.Name.Substring(0, n.Name.IndexOf(":"))}", false, true); + } + + var prop = GetProperty(t, n.Name); + if (prop != null) + { + var val = GetPropertyValue(type ?? prop.PropertyType, n); + if (val != null) + { + var propertySet = DelegateFactory.CreateSet(prop); + propertySet.Invoke(result, val); + continue; + } + } + + var field = GetField(t, n.Name); + if (field != null) + { + var val = GetPropertyValue(type ?? field.FieldType, n); + if (val != null) + { + var fieldSet = DelegateFactory.CreateSet(field); + fieldSet.Invoke(result, val); + } + } + } + + return result; + } + + FieldInfo GetField(Type t, string name) + { + IEnumerable fields; + cache.typeToFields.TryGetValue(t, out fields); + + if (fields == null) + { + return null; + } + + foreach (var f in fields) + { + if (f.Name == name) + { + return f; + } + } + + return null; + } + + object GetPropertyValue(Type type, XmlNode n) + { + if ((n.ChildNodes.Count == 1) && n.ChildNodes[0] is XmlCharacterData) + { + var text = n.ChildNodes[0].InnerText; + + var args = type.GetGenericArguments(); + if (args.Length == 1 && args[0].IsValueType) + { + if (args[0].GetGenericArguments().Any()) + { + return GetPropertyValue(args[0], n); + } + + var nullableType = typeof(Nullable<>).MakeGenericType(args); + if (type == nullableType) + { + if (text.Trim().ToLower() == "null" || string.IsNullOrWhiteSpace(text)) + { + return null; + } + + return GetPropertyValue(args[0], n); + } + } + + if (type == typeof(string)) + { + return text; + } + + if (type == typeof(bool)) + { + return XmlConvert.ToBoolean(text); + } + + if (type == typeof(byte)) + { + return XmlConvert.ToByte(text); + } + + if (type == typeof(char)) + { + return XmlConvert.ToChar(text); + } + + if (type == typeof(DateTime)) + { + return XmlConvert.ToDateTime(text, XmlDateTimeSerializationMode.RoundtripKind); + } + + if (type == typeof(DateTimeOffset)) + { + return XmlConvert.ToDateTimeOffset(text); + } + + if (type == typeof(decimal)) + { + return XmlConvert.ToDecimal(text); + } + + if (type == typeof(double)) + { + return XmlConvert.ToDouble(text); + } + + if (type == typeof(Guid)) + { + return XmlConvert.ToGuid(text); + } + + if (type == typeof(short)) + { + return XmlConvert.ToInt16(text); + } + + if (type == typeof(int)) + { + return XmlConvert.ToInt32(text); + } + + if (type == typeof(long)) + { + return XmlConvert.ToInt64(text); + } + + if (type == typeof(sbyte)) + { + return XmlConvert.ToSByte(text); + } + + if (type == typeof(float)) + { + return XmlConvert.ToSingle(text); + } + + if (type == typeof(TimeSpan)) + { + return XmlConvert.ToTimeSpan(text); + } + + if (type == typeof(ushort)) + { + return XmlConvert.ToUInt16(text); + } + + if (type == typeof(uint)) + { + return XmlConvert.ToUInt32(text); + } + + if (type == typeof(ulong)) + { + return XmlConvert.ToUInt64(text); + } + + if (type.IsEnum) + { + return Enum.Parse(type, text); + } + + if (type == typeof(byte[])) + { + return Convert.FromBase64String(text); + } + + if (type == typeof(Uri)) + { + return new Uri(text); + } + + if (!typeof(IEnumerable).IsAssignableFrom(type)) + { + if (n.ChildNodes[0] is XmlWhitespace) + { + return Activator.CreateInstance(type); + } + + throw new Exception($"Type not supported by the serializer: {type.AssemblyQualifiedName}"); + } + } + + if (typeof(XContainer).IsAssignableFrom(type)) + { + var reader = new StringReader(skipWrappingRawXml ? n.OuterXml : n.InnerXml); + + if (type == typeof(XDocument)) + { + return XDocument.Load(reader); + } + + return XElement.Load(reader); + } + + //Handle dictionaries + if (typeof(IDictionary).IsAssignableFrom(type)) + { + var result = Activator.CreateInstance(type) as IDictionary; + + var keyType = typeof(object); + var valueType = typeof(object); + + foreach (var interfaceType in type.GetInterfaces()) + { + var args = interfaceType.GetGenericArguments(); + if (args.Length != 2) + { + continue; + } + + if (typeof(IDictionary<,>).MakeGenericType(args[0], args[1]).IsAssignableFrom(type)) + { + keyType = args[0]; + valueType = args[1]; + break; + } + } + + foreach (XmlNode xn in n.ChildNodes) // go over KeyValuePairs + { + object key = null; + object value = null; + + foreach (XmlNode node in xn.ChildNodes) + { + if (node.Name == "Key") + { + key = GetObjectOfTypeFromNode(keyType, node); + } + if (node.Name == "Value") + { + value = GetObjectOfTypeFromNode(valueType, node); + } + } + + if (result != null && key != null) + { + result[key] = value; + } + } + + return result; + } + + if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string)) + { + var isArray = type.IsArray; + + var isISet = false; + if (type.IsGenericType && type.GetGenericArguments().Length == 1) + { + var setType = typeof(ISet<>).MakeGenericType(type.GetGenericArguments()); + isISet = setType.IsAssignableFrom(type); + } + + var typeToCreate = type; + if (isArray) + { + typeToCreate = cache.typesToCreateForArrays[type]; + } + + Type typeToCreateForEnumerables; + if (cache.typesToCreateForEnumerables.TryGetValue(type, out typeToCreateForEnumerables)) //handle IEnumerable + { + typeToCreate = typeToCreateForEnumerables; + } + + if (typeof(IList).IsAssignableFrom(typeToCreate)) + { + var list = Activator.CreateInstance(typeToCreate) as IList; + if (list != null) + { + foreach (XmlNode xn in n.ChildNodes) + { + if (xn.NodeType == XmlNodeType.Whitespace) + { + continue; + } + + var m = Process(xn, list); + list.Add(m); + } + + if (isArray) + { + return typeToCreate.GetMethod("ToArray").Invoke(list, null); + } + + if (isISet) + { + return Activator.CreateInstance(type, typeToCreate.GetMethod("ToArray").Invoke(list, null)); + } + } + + return list; + } + } + + if (n.ChildNodes.Count == 0) + { + if (type == typeof(string)) + { + return string.Empty; + } + return null; + } + + return GetObjectOfTypeFromNode(type, n); + } + + PropertyInfo GetProperty(Type t, string name) + { + IEnumerable properties; + if (!cache.typeToProperties.TryGetValue(t, out properties)) + { + cache.InitType(t); + cache.typeToProperties.TryGetValue(t, out properties); + } + + if (properties == null) + { + return null; + } + + var n = GetNameAfterColon(name); + + foreach (var prop in properties) + { + if (prop.Name == n) + { + return prop; + } + } + + return null; + } + + static string GetNameAfterColon(string name) + { + var n = name; + if (name.Contains(":")) + { + n = name.Substring(name.IndexOf(":") + 1, name.Length - name.IndexOf(":") - 1); + } + + return n; + } + + XmlSerializerCache cache; + string defaultNameSpace; + IMessageMapper mapper; + List messageBaseTypes = new List(); + IDictionary prefixesToNamespaces = new Dictionary(); + bool sanitizeInput; + bool skipWrappingRawXml; + + const string BASETYPE = "baseType"; + static ILog logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlMessageSerializer.cs b/src/NServiceBus.Core/Serializers/XML/XmlMessageSerializer.cs index 83a784e024e..e231bf1c74f 100644 --- a/src/NServiceBus.Core/Serializers/XML/XmlMessageSerializer.cs +++ b/src/NServiceBus.Core/Serializers/XML/XmlMessageSerializer.cs @@ -1,31 +1,30 @@ -namespace NServiceBus.Serializers.XML +namespace NServiceBus { using System; using System.Collections.Generic; using System.IO; using System.Linq; - using NServiceBus.MessageInterfaces; - using NServiceBus.Serialization; + using MessageInterfaces; + using Serialization; - /// - /// Implementation of the message serializer over XML supporting interface-based messages. - /// - public class XmlMessageSerializer : IMessageSerializer + class XmlMessageSerializer : IMessageSerializer { /// - /// Initializes an instance of a . + /// Initializes an instance of a . /// - /// Message Mapper + /// Message Mapper. /// The endpoint conventions. public XmlMessageSerializer(IMessageMapper mapper, Conventions conventions) { + Guard.AgainstNull(nameof(mapper), mapper); + Guard.AgainstNull(nameof(conventions), conventions); this.mapper = mapper; this.conventions = conventions; } /// - /// The namespace to place in outgoing XML. - /// If the provided namespace ends with trailing forward slashes, those will be removed on the fly. + /// The namespace to place in outgoing XML. + /// If the provided namespace ends with trailing forward slashes, those will be removed on the fly. /// public string Namespace { @@ -34,22 +33,22 @@ public string Namespace } /// - /// If true, then the serializer will use a sanitizing stream to skip invalid characters from the stream before parsing + /// If true, then the serializer will use a sanitizing stream to skip invalid characters from the stream before parsing. /// public bool SanitizeInput { get; set; } /// - /// Removes the wrapping of properties containing XDocument or XElement with property name as root element + /// Removes the wrapping of properties containing XDocument or XElement with property name as root element. /// public bool SkipWrappingRawXml { get; set; } /// - /// Deserializes from the given stream a set of messages. + /// Deserializes from the given stream a set of messages. /// /// Stream that contains messages. /// - /// The list of message types to deserialize. If null the types must be inferred - /// from the serialized data. + /// The list of message types to deserialize. If null the types must be inferred + /// from the serialized data. /// /// Deserialized messages. public object[] Deserialize(Stream stream, IList messageTypesToDeserialize = null) @@ -59,40 +58,29 @@ public object[] Deserialize(Stream stream, IList messageTypesToDeserialize return null; } - var deserializer = new Deserializer(mapper, cache) - { - SanitizeInput = SanitizeInput, - SkipWrappingRawXml = SkipWrappingRawXml, - }; - + var deserializer = new XmlDeserialization(mapper, cache, SkipWrappingRawXml, SanitizeInput); return deserializer.Deserialize(stream, messageTypesToDeserialize); } /// - /// Serializes the given messages to the given stream. + /// Gets the content type into which this serializer serializes the content to. /// - public void Serialize(object message, Stream stream) - { - var serializer = new Serializer(mapper, conventions, cache) - { - Namespace = Namespace, - SkipWrappingRawXml = SkipWrappingRawXml, - }; - - var buffer = serializer.Serialize(message); - stream.Write(buffer, 0, buffer.Length); - } + public string ContentType => ContentTypes.Xml; /// - /// Supported content type + /// Serializes the given messages to the given stream. /// - public string ContentType + public void Serialize(object message, Stream stream) { - get { return ContentTypes.Xml; } + var messageType = mapper.GetMappedTypeFor(message.GetType()); + using (var serializer = new XmlSerialization(messageType, stream, message, conventions, cache, SkipWrappingRawXml, Namespace)) + { + serializer.Serialize(); + } } /// - /// Scans the given type storing maps to fields and properties to save on reflection at runtime. + /// Scans the given type storing maps to fields and properties to save on reflection at runtime. /// public void InitType(Type t) { @@ -100,7 +88,7 @@ public void InitType(Type t) } /// - /// Initialized the serializer with the given message types + /// Initialized the serializer with the given message types. /// public void Initialize(IEnumerable types) { @@ -119,22 +107,14 @@ public void Initialize(IEnumerable types) string TrimPotentialTrailingForwardSlashes(string value) { - if (value == null) - { - return null; - } - - return value.TrimEnd(new[] - { - '/' - }); + return value?.TrimEnd('/'); } - readonly Conventions conventions; - readonly IMessageMapper mapper; - XmlSerializerCache cache = new XmlSerializerCache(); + Conventions conventions; + IMessageMapper mapper; + string nameSpace = "http://tempuri.net"; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlSanitizingStream.cs b/src/NServiceBus.Core/Serializers/XML/XmlSanitizingStream.cs index b5f992d7ee2..b0ee1f90187 100644 --- a/src/NServiceBus.Core/Serializers/XML/XmlSanitizingStream.cs +++ b/src/NServiceBus.Core/Serializers/XML/XmlSanitizingStream.cs @@ -1,201 +1,185 @@ -namespace NServiceBus.Serializers.XML { +namespace NServiceBus +{ using System; using System.IO; using System.Text; - /// - /// A StreamReader that excludes XML-illegal characters while reading. - /// - class XmlSanitizingStream : StreamReader - { - private const int EOF = -1; - - public XmlSanitizingStream(Stream streamToSanitize) - : base(streamToSanitize, true) - { } - - public static bool IsLegalXmlChar(string xmlVersion, int character) - { - switch (xmlVersion) - { - case "1.1": // http://www.w3.org/TR/xml11/#charsets - { - return - !( - character <= 0x8 || - character == 0xB || - character == 0xC || - (character >= 0xE && character<= 0x1F) || - (character >= 0x7F && character<= 0x84) || - (character >= 0x86 && character<= 0x9F) || - character > 0x10FFFF - ); - } - case "1.0": // http://www.w3.org/TR/REC-xml/#charsets - { - return - ( - character == 0x9 /* == '\t' == 9 */ || - character == 0xA /* == '\n' == 10 */ || - character == 0xD /* == '\r' == 13 */ || - (character >= 0x20 && character <= 0xD7FF ) || - (character >= 0xE000 && character <= 0xFFFD ) || - (character >= 0x10000 && character <= 0x10FFFF) - ); - } - default: - { - throw new ArgumentOutOfRangeException - ("xmlVersion", string.Format("'{0}' is not a valid XML version.", xmlVersion)); - } - } - } - - /// - /// Get whether an integer represents a legal XML 1.0 character. See the - /// specification at w3.org for these characters. - /// - public static bool IsLegalXmlChar(int character) - { - return IsLegalXmlChar("1.0", character); - } - - public override int Read() - { - // Read each character, skipping over characters that XML has prohibited - - int nextCharacter; - - do - { - // Read a character - - if ((nextCharacter = base.Read()) == EOF) - { - // If the character denotes the end of the file, stop reading - - break; - } - } - - // Skip the character if it's prohibited, and try the next - - while (!IsLegalXmlChar(nextCharacter)); - - return nextCharacter; - } - - public override int Peek() - { - // Return the next legal XML character without reading it - - int nextCharacter; - - do - { - // See what the next character is - - nextCharacter = base.Peek(); - } - while - ( - // If it's prohibited XML, skip over the character in the stream - // and try the next. - - !IsLegalXmlChar(nextCharacter) && - (nextCharacter = base.Read()) != EOF - ); - - return nextCharacter; - - } // method - - // The following methods are exact copies of the methods in TextReader, - // extracting by disassembling it in Reflector - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException("index"); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException("count"); - } - if ((buffer.Length - index) < count) - { - throw new ArgumentException(); - } - var number = 0; - do - { - var nextNumber = Read(); - if (nextNumber == -1) - { - return number; - } - buffer[index + number++] = (char)nextNumber; - } - while (number < count); - return number; - } - - public override int ReadBlock(char[] buffer, int index, int count) - { - int number; - var nextNumber = 0; - do - { - nextNumber += number = Read(buffer, index + nextNumber, count - nextNumber); - } - while ((number > 0) && (nextNumber < count)); - return nextNumber; - } - - public override string ReadLine() - { - var builder = new StringBuilder(); - while (true) - { - var number = Read(); - switch (number) - { - case -1: - if (builder.Length > 0) - { - return builder.ToString(); - } - return null; - - case 13: - case 10: - if ((number == 13) && (Peek() == 10)) - { - Read(); - } - return builder.ToString(); - } - builder.Append((char)number); - } - } - - public override string ReadToEnd() - { - int number; - var buffer = new char[0x1000]; - var builder = new StringBuilder(0x1000); - while ((number = Read(buffer, 0, buffer.Length)) != 0) - { - builder.Append(buffer, 0, number); - } - return builder.ToString(); - } + // A StreamReader that excludes XML-illegal characters while reading. + class XmlSanitizingStream : StreamReader + { + public XmlSanitizingStream(Stream streamToSanitize) + : base(streamToSanitize, true) + { + } + + public static bool IsLegalXmlChar(string xmlVersion, int character) + { + switch (xmlVersion) + { + case "1.1": // http://www.w3.org/TR/xml11/#charsets + { + return + !( + character <= 0x8 || + character == 0xB || + character == 0xC || + (character >= 0xE && character <= 0x1F) || + (character >= 0x7F && character <= 0x84) || + (character >= 0x86 && character <= 0x9F) || + character > 0x10FFFF + ); + } + case "1.0": // http://www.w3.org/TR/REC-xml/#charsets + { + return + character == 0x9 /* == '\t' == 9 */|| + character == 0xA /* == '\n' == 10 */|| + character == 0xD /* == '\r' == 13 */|| + (character >= 0x20 && character <= 0xD7FF) || + (character >= 0xE000 && character <= 0xFFFD) || + (character >= 0x10000 && character <= 0x10FFFF); + } + default: + { + throw new ArgumentOutOfRangeException + (nameof(xmlVersion), $"'{xmlVersion}' is not a valid XML version."); + } + } + } + + /// + /// Get whether an integer represents a legal XML 1.0 character. See the + /// specification at w3.org for these characters. + /// + public static bool IsLegalXmlChar(int character) + { + return IsLegalXmlChar("1.0", character); + } + + public override int Read() + { + // Read each character, skipping over characters that XML has prohibited + + int nextCharacter; + + do + { + // Read a character + + if ((nextCharacter = base.Read()) == EOF) + { + // If the character denotes the end of the file, stop reading + + break; + } + } + + // Skip the character if it's prohibited, and try the next - } + while (!IsLegalXmlChar(nextCharacter)); + + return nextCharacter; + } + + public override int Peek() + { + // Return the next legal XML character without reading it + + int nextCharacter; + + do + { + // See what the next character is + + nextCharacter = base.Peek(); + } while + ( + // If it's prohibited XML, skip over the character in the stream + // and try the next. + + !IsLegalXmlChar(nextCharacter) && + (nextCharacter = base.Read()) != EOF + ); + + return nextCharacter; + } // method + + // The following methods are exact copies of the methods in TextReader, + // extracting by disassembling it in Reflector + + public override int Read(char[] buffer, int index, int count) + { + Guard.AgainstNull(nameof(buffer), buffer); + Guard.AgainstNegative(nameof(index), index); + Guard.AgainstNegative(nameof(count), count); + + if (buffer.Length - index < count) + { + throw new ArgumentException(); + } + var number = 0; + do + { + var nextNumber = Read(); + if (nextNumber == -1) + { + return number; + } + buffer[index + number++] = (char) nextNumber; + } while (number < count); + return number; + } + + public override int ReadBlock(char[] buffer, int index, int count) + { + int number; + var nextNumber = 0; + do + { + nextNumber += number = Read(buffer, index + nextNumber, count - nextNumber); + } while ((number > 0) && (nextNumber < count)); + return nextNumber; + } + + public override string ReadLine() + { + var builder = new StringBuilder(); + while (true) + { + var number = Read(); + switch (number) + { + case -1: + if (builder.Length > 0) + { + return builder.ToString(); + } + return null; + + case 13: + case 10: + if ((number == 13) && (Peek() == 10)) + { + Read(); + } + return builder.ToString(); + } + builder.Append((char) number); + } + } + + public override string ReadToEnd() + { + int number; + var buffer = new char[0x1000]; + var builder = new StringBuilder(0x1000); + while ((number = Read(buffer, 0, buffer.Length)) != 0) + { + builder.Append(buffer, 0, number); + } + return builder.ToString(); + } -} + const int EOF = -1; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlSerialization.cs b/src/NServiceBus.Core/Serializers/XML/XmlSerialization.cs new file mode 100644 index 00000000000..e81e59bca5b --- /dev/null +++ b/src/NServiceBus.Core/Serializers/XML/XmlSerialization.cs @@ -0,0 +1,338 @@ +namespace NServiceBus +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Xml; + using System.Xml.Linq; + + class XmlSerialization : IDisposable + { + public XmlSerialization(Type messageType, Stream stream, object message, Conventions conventions, XmlSerializerCache cache, bool skipWrappingRawXml, string @namespace = DefaultNamespace) + { + this.messageType = messageType; + this.message = message; + this.conventions = conventions; + this.cache = cache; + this.skipWrappingRawXml = skipWrappingRawXml; + this.@namespace = @namespace; + writer = new RawXmlTextWriter(stream, new XmlWriterSettings + { + CloseOutput = false + }); + } + + public void Dispose() + { + //Injected at compile time + } + + public void Serialize() + { + var doc = new XDocument(new XDeclaration("1.0", null, null)); + + var elementName = messageType.SerializationFriendlyName(); + doc.Add(new XElement(elementName)); + WriteObject(doc.Root, elementName, messageType, message, true); + + SetDefaultNamespace(doc.Root, $"{@namespace}/{messageType.Namespace}"); + ForceEmptyTagsWithNewlines(doc); + + doc.WriteTo(writer); + writer.Flush(); + } + + static void ForceEmptyTagsWithNewlines(XDocument document) + { + // this is to force compatibility with previous implementation, + // in particular, to support nested objects with null properties in them. + + foreach (var childElement in + from x in document.DescendantNodes().OfType() + where x.IsEmpty && !x.HasAttributes + select x) + { + childElement.Value = "\n"; + } + } + + static void SetDefaultNamespace(XElement element, XNamespace newXmlns) + { + var currentXmlns = element.GetDefaultNamespace(); + if (currentXmlns == newXmlns) + { + return; + } + + foreach (var descendant in element.DescendantsAndSelf() + .Where(e => e.Name.Namespace == currentXmlns)) + { + descendant.Name = newXmlns.GetName(descendant.Name.LocalName); + } + } + + List GetBaseTypes() + { + var result = new List(); + var baseType = messageType.BaseType; + while (baseType != typeof(object) && baseType != null) + { + if (conventions.IsMessageType(baseType)) + { + if (!result.Contains(baseType.FullName)) + { + result.Add(baseType.FullName); + } + } + + baseType = baseType.BaseType; + } + + foreach (var i in messageType.GetInterfaces()) + { + if (conventions.IsMessageType(i)) + { + if (!result.Contains(i.FullName)) + { + result.Add(i.FullName); + } + } + } + + return result; + } + + void WriteObject(XElement elem, string name, Type type, object value, bool useNS = false) + { + if (type == typeof(object) && value.GetType().IsSimpleType()) + { + var typeOfValue = value.GetType(); + var ns = (XNamespace) typeOfValue.Name; + var prefix = typeOfValue.Name.ToLower(); + if (!elem.Attributes().Any(a => a.IsNamespaceDeclaration && a.Name.LocalName == prefix)) + { + elem.Add(new XAttribute(XNamespace.Xmlns + prefix, ns.NamespaceName)); + } + + elem.Add(new XElement(ns + name, value)); + + return; + } + + if (useNS) + { + var baseTypes = GetBaseTypes(); + WriteElementNamespaces(elem, baseTypes); + } + else + { + var xe = new XElement(name); + elem.Add(xe); + elem = xe; + } + + Write(elem, type, value); + } + + void Write(XElement elem, Type t, object obj) + { + if (obj == null) + { + // For null entries in a nullable array + // See https://github.com/Particular/NServiceBus/issues/2706 + if (t.IsNullableType()) + { + elem.Value = "null"; + } + + return; + } + + IEnumerable properties; + if (!cache.typeToProperties.TryGetValue(t, out properties)) + { + cache.InitType(t); + cache.typeToProperties.TryGetValue(t, out properties); + } + + foreach (var prop in properties) + { + if (IsIndexedProperty(prop)) + { + throw new NotSupportedException($"Type {t.FullName} contains an indexed property named {prop.Name}. Indexed properties are not supported on message types."); + } + WriteEntry(elem, prop.Name, prop.PropertyType, DelegateFactory.CreateGet(prop).Invoke(obj)); + } + + foreach (var field in cache.typeToFields[t]) + { + WriteEntry(elem, field.Name, field.FieldType, DelegateFactory.CreateGet(field).Invoke(obj)); + } + } + + void WriteEntry(XElement elem, string name, Type type, object value) + { + if (value == null) + { + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + return; + } + + var args = type.GetGenericArguments(); + if (args.Length == 1 && args[0].IsValueType) + { + var nullableType = typeof(Nullable<>).MakeGenericType(args); + if (type == nullableType) + { + elem.Add(new XElement(name, new XAttribute(xsiNamespace + "nil", true), null)); + + return; + } + } + + return; + } + + if (typeof(XContainer).IsAssignableFrom(type)) + { + var container = (XContainer) value; + if (skipWrappingRawXml) + { + elem.Add(XElement.Parse(container.ToString())); + } + else + { + elem.Add(new XElement(name, XElement.Parse(container.ToString()))); + } + + return; + } + + if (type.IsValueType || type == typeof(string) || type == typeof(Uri) || type == typeof(char)) + { + elem.Add(new XElement(name, value)); + return; + } + + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + var xe = new XElement(name); + + if (type == typeof(byte[])) + { + var base64String = Convert.ToBase64String((byte[]) value); + xe.Value = base64String; + } + else + { + var baseType = typeof(object); + + var interfaces = type.GetInterfaces(); + if (type.IsInterface) + { + interfaces = interfaces.Union(new[] + { + type + }).ToArray(); + } + + //Get generic type from list: T for List, KeyValuePair for IDictionary + foreach (var interfaceType in interfaces) + { + var arr = interfaceType.GetGenericArguments(); + if (arr.Length != 1) + { + continue; + } + + if (typeof(IEnumerable<>).MakeGenericType(arr[0]).IsAssignableFrom(type)) + { + baseType = arr[0]; + break; + } + } + + foreach (var obj in (IEnumerable) value) + { + if (obj != null && obj.GetType().IsSimpleType()) + { + WriteEntry(xe, obj.GetType().Name, obj.GetType(), obj); + } + else + { + WriteObject(xe, baseType.SerializationFriendlyName(), baseType, obj); + } + } + } + + elem.Add(xe); + return; + } + + WriteObject(elem, name, type, value); + } + + static bool IsIndexedProperty(PropertyInfo propertyInfo) + { + return propertyInfo?.GetIndexParameters().Length > 0; + } + + void WriteElementNamespaces(XElement elem, IReadOnlyList baseTypes) + { + elem.Add(new XAttribute(XNamespace.Xmlns + "xsi", xsiNamespace), + new XAttribute(XNamespace.Xmlns + "xsd", xsdNamespace)); + + for (var i = 0; i < baseTypes.Count; i++) + { + var prefix = BaseType; + if (i != 0) + { + prefix += i; + } + + elem.Add(new XAttribute(XNamespace.Xmlns + prefix, baseTypes[i])); + } + } + + XmlSerializerCache cache; + Conventions conventions; + object message; + + Type messageType; + string @namespace; + bool skipWrappingRawXml; + XmlWriter writer; + const string BaseType = "baseType"; + + const string DefaultNamespace = "http://tempuri.net"; + static XNamespace xsiNamespace = "http://www.w3.org/2001/XMLSchema-instance"; + static XNamespace xsdNamespace = "http://www.w3.org/2001/XMLSchema"; + + class RawXmlTextWriter : XmlTextWriter + { + public RawXmlTextWriter(Stream w, XmlWriterSettings settings) : base(w, null /*writes UTF-8 and omits the 'encoding' attribute in XML declaration*/) + { + this.settings = settings; + } + + public override void WriteEndElement() + { + WriteFullEndElement(); + } + + public override void Close() + { + if (settings.CloseOutput) + { + base.Close(); + } + } + + readonly XmlWriterSettings settings; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlSerializer.cs b/src/NServiceBus.Core/Serializers/XML/XmlSerializer.cs new file mode 100644 index 00000000000..85c5bd27c56 --- /dev/null +++ b/src/NServiceBus.Core/Serializers/XML/XmlSerializer.cs @@ -0,0 +1,55 @@ +namespace NServiceBus +{ + using System; + using System.Linq; + using MessageInterfaces; + using Serialization; + using Settings; + using Unicast.Messages; + + /// + /// Defines the capabilities of the XML serializer. + /// + public class XmlSerializer : SerializationDefinition + { + /// + /// Provides a factory method for building a message serializer. + /// + public override Func Configure(ReadOnlySettings settings) + { + return mapper => + { + var conventions = settings.Get(); + var serializer = new XmlMessageSerializer(mapper, conventions); + + string customNamespace; + if (settings.TryGet(CustomNamespaceConfigurationKey, out customNamespace)) + { + serializer.Namespace = customNamespace; + } + + bool skipWrappingRawXml; + if (settings.TryGet(SkipWrappingRawXml, out skipWrappingRawXml)) + { + serializer.SkipWrappingRawXml = skipWrappingRawXml; + } + + bool sanitizeInput; + if (settings.TryGet(SanitizeInput, out sanitizeInput)) + { + serializer.SanitizeInput = sanitizeInput; + } + + var registry = settings.Get(); + var messageTypes = registry.GetAllMessages().Select(m => m.MessageType); + + serializer.Initialize(messageTypes); + return serializer; + }; + } + + internal const string CustomNamespaceConfigurationKey = "XmlSerializer.CustomNamespace"; + internal const string SkipWrappingRawXml = "XmlSerializer.SkipWrappingRawXml"; + internal const string SanitizeInput = "XmlSerializer.SkipWrappingRawXml"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Serializers/XML/XmlSerializerCache.cs b/src/NServiceBus.Core/Serializers/XML/XmlSerializerCache.cs index fad0f3cb674..c90fc5a6236 100644 --- a/src/NServiceBus.Core/Serializers/XML/XmlSerializerCache.cs +++ b/src/NServiceBus.Core/Serializers/XML/XmlSerializerCache.cs @@ -1,20 +1,20 @@ -namespace NServiceBus.Serializers.XML +namespace NServiceBus { using System; using System.Collections; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml.Linq; using System.Xml.Serialization; - using NServiceBus.Logging; - using NServiceBus.Utils.Reflection; + using Logging; class XmlSerializerCache { public void InitType(Type t) { - logger.Debug("Initializing type: " + t.AssemblyQualifiedName); + logger.Debug($"Initializing type: {t.AssemblyQualifiedName}"); if (t.IsSimpleType()) { @@ -90,7 +90,7 @@ public void InitType(Type t) var args = t.GetGenericArguments(); if (args.Length == 2) { - isKeyValuePair = (typeof(KeyValuePair<,>).MakeGenericType(args[0], args[1]) == t); + isKeyValuePair = typeof(KeyValuePair<,>).MakeGenericType(args[0], args[1]) == t; } if (args.Length == 1 && args[0].IsValueType) @@ -121,32 +121,16 @@ public void InitType(Type t) foreach (var p in props) { - logger.Debug("Handling property: " + p.Name); - - DelegateFactory.CreateGet(p); - if (!isKeyValuePair) - { - DelegateFactory.CreateSet(p); - } - InitType(p.PropertyType); } foreach (var f in fields) { - logger.Debug("Handling field: " + f.Name); - - DelegateFactory.CreateGet(f); - if (!isKeyValuePair) - { - DelegateFactory.CreateSet(f); - } - InitType(f.FieldType); } } - IEnumerable GetAllPropertiesForType(Type t, bool isKeyValuePair) + PropertyInfo[] GetAllPropertiesForType(Type t, bool isKeyValuePair) { var result = new List(); @@ -164,7 +148,7 @@ IEnumerable GetAllPropertiesForType(Type t, bool isKeyValuePair) if (typeof(IList) == prop.PropertyType) { - throw new NotSupportedException("IList is not a supported property type for serialization, use List instead. Type: " + t.FullName + " Property: " + prop.Name); + throw new NotSupportedException($"IList is not a supported property type for serialization, use List instead. Type: {t.FullName} Property: {prop.Name}"); } var args = prop.PropertyType.GetGenericArguments(); @@ -173,11 +157,11 @@ IEnumerable GetAllPropertiesForType(Type t, bool isKeyValuePair) { if (typeof(IList<>).MakeGenericType(args) == prop.PropertyType) { - throw new NotSupportedException("IList is not a supported property type for serialization, use List instead. Type: " + t.FullName + " Property: " + prop.Name); + throw new NotSupportedException($"IList is not a supported property type for serialization, use List instead. Type: {t.FullName} Property: {prop.Name}"); } if (typeof(ISet<>).MakeGenericType(args) == prop.PropertyType) { - throw new NotSupportedException("ISet is not a supported property type for serialization, use HashSet instead. Type: " + t.FullName + " Property: " + prop.Name); + throw new NotSupportedException($"ISet is not a supported property type for serialization, use HashSet instead. Type: {t.FullName} Property: {prop.Name}"); } } @@ -185,12 +169,12 @@ IEnumerable GetAllPropertiesForType(Type t, bool isKeyValuePair) { if (typeof(IDictionary<,>).MakeGenericType(args[0], args[1]) == prop.PropertyType) { - throw new NotSupportedException("IDictionary is not a supported property type for serialization, use Dictionary instead. Type: " + t.FullName + " Property: " + prop.Name + ". Consider using a concrete Dictionary instead, where T and K cannot be of type 'System.Object'"); + throw new NotSupportedException($"IDictionary is not a supported property type for serialization, use Dictionary instead. Type: {t.FullName} Property: {prop.Name}. Consider using a concrete Dictionary instead, where T and K cannot be of type 'System.Object'"); } if (args[0].FullName == "System.Object" || args[1].FullName == "System.Object") { - throw new NotSupportedException("Dictionary is not a supported when Key or Value is of Type System.Object. Type: " + t.FullName + " Property: " + prop.Name + ". Consider using a concrete Dictionary where T and K are not of type 'System.Object'"); + throw new NotSupportedException($"Dictionary is not a supported when Key or Value is of Type System.Object. Type: {t.FullName} Property: {prop.Name}. Consider using a concrete Dictionary where T and K are not of type 'System.Object'"); } } @@ -205,19 +189,20 @@ IEnumerable GetAllPropertiesForType(Type t, bool isKeyValuePair) } } - return result.Distinct(); + return result.Distinct().ToArray(); } - IEnumerable GetAllFieldsForType(Type t) + FieldInfo[] GetAllFieldsForType(Type t) { return t.GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public); } - public readonly Dictionary> typeToFields = new Dictionary>(); - public readonly Dictionary> typeToProperties = new Dictionary>(); - readonly List typesBeingInitialized = new List(); - public readonly Dictionary typesToCreateForArrays = new Dictionary(); - public readonly Dictionary typesToCreateForEnumerables = new Dictionary(); + List typesBeingInitialized = new List(); + public ConcurrentDictionary typesToCreateForArrays = new ConcurrentDictionary(); + public ConcurrentDictionary typesToCreateForEnumerables = new ConcurrentDictionary(); + + public ConcurrentDictionary> typeToFields = new ConcurrentDictionary>(); + public ConcurrentDictionary> typeToProperties = new ConcurrentDictionary>(); static ILog logger = LogManager.GetLogger(); } diff --git a/src/NServiceBus.Core/Settings/DurableMessagesConfig.cs b/src/NServiceBus.Core/Settings/DurableMessagesConfig.cs deleted file mode 100644 index 51869e0c94b..00000000000 --- a/src/NServiceBus.Core/Settings/DurableMessagesConfig.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NServiceBus -{ - using NServiceBus.Settings; - - /// - /// Configuration class for durable messaging. - /// - public static class DurableMessagesConfig - { - - /// - /// Configures messages to be guaranteed to be delivered in the event of a computer failure or network problem. - /// - public static void EnableDurableMessages(this BusConfiguration config) - { - config.Settings.Set("Endpoint.DurableMessages", true); - } - - /// - /// Configures messages that are not guaranteed to be delivered in the event of a computer failure or network problem. - /// - public static void DisableDurableMessages(this BusConfiguration config) - { - config.Settings.Set("Endpoint.DurableMessages", false); - } - - internal static bool GetDurableMessagesEnabled(ReadOnlySettings settings) - { - bool durableMessagesEnabled; - if (settings.TryGet("Endpoint.DurableMessages", out durableMessagesEnabled)) - { - return durableMessagesEnabled; - } - return true; - } - - /// - /// Returns whether durable messages are on or off. - /// - public static bool DurableMessagesEnabled(this Configure config) - { - return GetDurableMessagesEnabled(config.Settings); - } - } -} diff --git a/src/NServiceBus.Core/Settings/ReadOnlySettings.cs b/src/NServiceBus.Core/Settings/ReadOnlySettings.cs index f023c7b38d8..752abb18fe1 100644 --- a/src/NServiceBus.Core/Settings/ReadOnlySettings.cs +++ b/src/NServiceBus.Core/Settings/ReadOnlySettings.cs @@ -1,7 +1,5 @@ namespace NServiceBus.Settings { - using ObjectBuilder; - /// /// Settings for readonly. /// @@ -10,10 +8,10 @@ public interface ReadOnlySettings /// /// Gets the setting value. /// - /// The to locate in the . + /// The to locate in the . /// The setting value. T Get(); - + /// /// Gets the setting value. /// @@ -25,8 +23,8 @@ public interface ReadOnlySettings /// /// Safely get the settings value, returning false if the settings key was not found. /// - /// The type to get, fullname will be used as key - /// The value if present + /// The type to get, fullname will be used as key. + /// The value if present. bool TryGet(out T val); /// @@ -44,46 +42,50 @@ public interface ReadOnlySettings object Get(string key); /// - /// Gets the setting value or the default(T). + /// Gets the setting or default based on the typename. + /// + /// The setting to get. + /// The actual value. + T GetOrDefault(); + + /// + /// Gets the setting value or the default(T).. /// /// The value of the setting. /// The key of the setting to get. /// The setting value. T GetOrDefault(string key); - + /// - /// Determines whether the contains the specified key. + /// Determines whether the contains the specified key. /// - /// The key to locate in the - /// true if the contains an element with the specified key; otherwise, false. + /// The key to locate in the . + /// true if the contains an element with the specified key; otherwise, false. bool HasSetting(string key); - + /// - /// Determines whether the contains the specified . + /// Determines whether the contains the specified . /// - /// The to locate in the . - /// true if the contains an element with the specified key; otherwise, false. + /// The to locate in the . + /// true if the contains an element with the specified key; otherwise, false. bool HasSetting(); /// - /// Determines whether the contains a specific value for the specified key. + /// Determines whether the contains a specific value for the specified key. /// - /// The key to locate in the - /// true if the contains an explicit value with the specified key; otherwise, false. + /// The key to locate in the . + /// + /// true if the contains an explicit value with the specified key; otherwise, + /// false. + /// bool HasExplicitValue(string key); /// - /// Determines whether the contains a specific value for the specified . + /// Determines whether the contains a specific value for the specified + /// . /// - /// The to locate in the . - /// true if the contains an element with the specified key; otherwise, false. + /// The to locate in the . + /// true if the contains an element with the specified key; otherwise, false. bool HasExplicitValue(); - - /// - /// Setup property injection for the given type based on convention - /// - /// - /// - void ApplyTo(IComponentConfig config); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Settings/ScaleOutExtentions.cs b/src/NServiceBus.Core/Settings/ScaleOutExtentions.cs deleted file mode 100644 index b5b31455c37..00000000000 --- a/src/NServiceBus.Core/Settings/ScaleOutExtentions.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus -{ - using System; - using Settings; - - /// - /// Provides a fluent api to allow the configuration of . - /// - public static class ScaleOutExtentions - { - /// - /// Allows the user to control how the current endpoint behaves when scaled out. - /// - public static ScaleOutSettings ScaleOut(this BusConfiguration config) - { - return new ScaleOutSettings(config); - } - -#pragma warning disable 1591 - // ReSharper disable UnusedParameter.Global - [ObsoleteEx( - Message = "Use `configuration.ScaleOut().UseSingleBrokerQueue()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure ScaleOut(this Configure config, Action customScaleOutSettings) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Settings/ScaleOutSettings.cs b/src/NServiceBus.Core/Settings/ScaleOutSettings.cs deleted file mode 100644 index 4b8842d5c66..00000000000 --- a/src/NServiceBus.Core/Settings/ScaleOutSettings.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus.Settings -{ - using System; - - /// - /// Placeholder for the various settings related to how a endpoint is scaled out - /// - public class ScaleOutSettings - { - BusConfiguration config; - - internal ScaleOutSettings(BusConfiguration config) - { - this.config = config; - } - - /// - /// Instructs the broker based transports to use a single queue for the endpoint regardless of which machine its running on. - /// This is suitable for backend processing endpoints and is the default for the As_aServer role. - /// Clients that needs to make use of callbacks needs to make sure that this setting is off since they need to have a unique - /// input queue per machine in order to not miss any of the callbacks. - /// - public void UseSingleBrokerQueue() - { - config.Settings.Set("ScaleOut.UseSingleBrokerQueue", true); - } - - /// - /// Instructs the broker based transports to use a separate queue per endpoint when running on multiple machines. - /// This allows clients to make use of callbacks. This setting is the default. - /// - public void UseUniqueBrokerQueuePerMachine() - { - config.Settings.Set("ScaleOut.UseSingleBrokerQueue", false); - } - - /// - /// Makes sure that each instance of this endpoint gets a unique queue based on the transport specific discriminator. - /// The default discriminator set by the transport will be used - /// - public void UniqueQueuePerEndpointInstance() - { - config.Settings.Set("IndividualizeEndpointAddress", true); - } - - /// - /// Makes sure that each instance of this endpoint gets a unique queue based on the transport specific discriminator. - /// - /// The discriminator to use - public void UniqueQueuePerEndpointInstance(string discriminator) - { - if (discriminator == null) - { - throw new ArgumentException("Discriminator can't be null"); - } - - config.Settings.Set("EndpointInstanceDiscriminator", discriminator); - UniqueQueuePerEndpointInstance(); - } - } -} diff --git a/src/NServiceBus.Core/Settings/SettingsHolder.cs b/src/NServiceBus.Core/Settings/SettingsHolder.cs index 7497f50813e..c960b68976d 100644 --- a/src/NServiceBus.Core/Settings/SettingsHolder.cs +++ b/src/NServiceBus.Core/Settings/SettingsHolder.cs @@ -4,44 +4,44 @@ namespace NServiceBus.Settings using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; - using System.Linq.Expressions; - using NServiceBus.ObjectBuilder; - using NServiceBus.Utils.Reflection; /// /// Setting container. /// - public class SettingsHolder : ReadOnlySettings + public partial class SettingsHolder : ReadOnlySettings { /// - /// Gets the given setting by key + /// Gets the given setting by key. /// - /// The type of the value - /// The key + /// The type of the value. + /// The key. public T Get(string key) { - return (T)Get(key); + Guard.AgainstNullAndEmpty(nameof(key), key); + return (T) Get(key); } /// - /// Tries to get the given value, key is the type fullname + /// Tries to get the given value, key is the type fullname. /// - /// The type - /// The returned value if present - /// True if found + /// The type. + /// The returned value if present. + /// True if found. public bool TryGet(out T val) { return TryGet(typeof(T).FullName, out val); } + /// - /// Tries to get the given value by key + /// Tries to get the given value by key. /// - /// The type - /// The key - /// Value if found - /// True if key is found + /// The type. + /// The key. + /// Value if found. + /// True if key is found. public bool TryGet(string key, out T val) { + Guard.AgainstNullAndEmpty(nameof(key), key); val = default(T); object tmp; @@ -54,29 +54,32 @@ public bool TryGet(string key, out T val) } if (!(tmp is T)) + { return false; + } - val = (T)tmp; + val = (T) tmp; return true; } /// - /// Gets the given value, key is type fullname + /// Gets the given value, key is type fullname. /// - /// The type - /// The value if found, throws if not + /// The type. + /// The value if found, throws if not. public T Get() { - return (T)Get(typeof(T).FullName); + return (T) Get(typeof(T).FullName); } /// - /// Gets the given value by key + /// Gets the given value by key. /// - /// The key - /// The value + /// The key. + /// The value. public object Get(string key) { + Guard.AgainstNullAndEmpty(nameof(key), key); object result; if (Overrides.TryGetValue(key, out result)) { @@ -88,207 +91,236 @@ public object Get(string key) return result; } - throw new KeyNotFoundException(String.Format("The given key ({0}) was not present in the dictionary.", key)); + throw new KeyNotFoundException($"The given key ({key}) was not present in the dictionary."); } /// - /// Sets the setting value. + /// Gets the setting or default based on the typename. /// - /// The key to use to store the setting. - /// The setting value. - public void Set(string key, object value) + /// The setting to get. + /// The actual value. + public T GetOrDefault() { - EnsureWriteEnabled(key); - - Overrides[key] = value; + return GetOrDefault(typeof(T).FullName); } /// - /// Sets the value + /// Gets the value or its default. /// - /// The type to use as a key for storing the setting. - /// The setting value. - public void Set(object value) + /// The type. + /// The key. + /// The value. + public T GetOrDefault(string key) { - Set(typeof(T).FullName, value); + Guard.AgainstNullAndEmpty(nameof(key), key); + object result; + if (Overrides.TryGetValue(key, out result)) + { + return (T) result; + } + + if (Defaults.TryGetValue(key, out result)) + { + return (T) result; + } + + return default(T); } + /// - /// Sets the given value, key is type fullname + /// True if there is a default or explicit value for the given key. /// - /// The type - /// Action to store - public void Set(Action value) + /// The Key. + /// True if found. + public bool HasSetting(string key) { - Set(typeof(T).FullName, value); + Guard.AgainstNullAndEmpty(nameof(key), key); + return Overrides.ContainsKey(key) || Defaults.ContainsKey(key); } /// - /// Sets the value of the given property + /// True if there is a setting for the given type. /// - public void SetProperty(Expression> property, object value) + /// The type. + /// True if found. + public bool HasSetting() { - var prop = Reflect.GetProperty(property); + var key = typeof(T).FullName; - Set(typeof(T).FullName + "." + prop.Name, value); + return HasSetting(key); } /// - /// Sets the default value of the given property + /// True if there is an explicit value for the given key. /// - public void SetPropertyDefault(Expression> property, object value) + /// The Key. + /// True if found. + public bool HasExplicitValue(string key) { - var prop = Reflect.GetProperty(property); - - SetDefault(typeof(T).FullName + "." + prop.Name, value); + Guard.AgainstNullAndEmpty(nameof(key), key); + return Overrides.ContainsKey(key); } /// - /// Sets the default setting value. + /// True if there is an explicit value for the given type. /// - /// The type to use as a key for storing the setting. - /// The setting value. - public void SetDefault(object value) + /// The type. + /// True if found. + public bool HasExplicitValue() { - SetDefault(typeof(T).FullName, value); + var key = typeof(T).FullName; + + return HasExplicitValue(key); } /// - /// Sets the default value for the given setting + /// Gets the requested value, a new one will be created and added if needed. /// - /// The type - /// The value to store as default - public void SetDefault(Action value) + public T GetOrCreate() + where T : class, new() { - SetDefault(typeof(T).FullName, value); + T value; + if (!TryGet(out value)) + { + value = new T(); + Set(value); + } + return value; } /// - /// Set the default value for the given key + /// Sets the setting value. /// - /// The key - /// The value - public void SetDefault(string key, object value) + /// The key to use to store the setting. + /// The setting value. + public void Set(string key, object value) { + Guard.AgainstNullAndEmpty(nameof(key), key); EnsureWriteEnabled(key); - Defaults[key] = value; + Overrides[key] = value; } /// - /// Gets the setting or default based on the typename + /// Sets the value. /// - /// The setting to get - /// The actual value - public T GetOrDefault() + /// The type to use as a key for storing the setting. + /// The setting value. + public void Set(object value) { - return GetOrDefault(typeof(T).FullName); + Set(typeof(T).FullName, value); } /// - /// Gets the value or its default + /// Sets the given value, key is type fullname. /// - /// The type - /// The key - /// The value - public T GetOrDefault(string key) + /// The type. + /// Action to store. + public void Set(Action value) { - object result; - if (Overrides.TryGetValue(key, out result)) - { - return (T)result; - } - - if (Defaults.TryGetValue(key, out result)) - { - return (T)result; - } - - return default(T); + Set(typeof(T).FullName, value); } /// - /// True if there is a default or explicit value for the given key + /// Sets the default setting value. /// - /// The Key - /// True if found - public bool HasSetting(string key) + /// The type to use as a key for storing the setting. + /// The setting value. + public void SetDefault(object value) { - return Overrides.ContainsKey(key) || Defaults.ContainsKey(key); + SetDefault(typeof(T).FullName, value); } /// - /// True if there is a setting for the given type + /// Sets the default value for the given setting. /// - /// The type - /// True if found - public bool HasSetting() + /// The type. + /// The value to store as default. + public void SetDefault(Action value) { - var key = typeof(T).FullName; - - return HasSetting(key); + SetDefault(typeof(T).FullName, value); } /// - /// True if there is an explicit value for the given key + /// Set the default value for the given key. /// - /// The Key - /// True if found - public bool HasExplicitValue(string key) + /// The key. + /// The value. + public void SetDefault(string key, object value) { - return Overrides.ContainsKey(key); + Guard.AgainstNullAndEmpty(nameof(key), key); + EnsureWriteEnabled(key); + + Defaults[key] = value; } /// - /// True if there is an explicit value for the given type + /// Locks the settings to prevent further modifications. /// - /// The type - /// True if found - public bool HasExplicitValue() + internal void PreventChanges() { - var key = typeof(T).FullName; + locked = true; + } - return HasExplicitValue(key); + internal void Merge(ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + + EnsureMergingIsPossible(); + + var holder = settings as SettingsHolder ?? new SettingsHolder(); + + foreach (var @default in holder.Defaults) + { + Defaults[@default.Key] = @default.Value; + } + + foreach (var @override in holder.Overrides) + { + Overrides[@override.Key] = @override.Value; + } } - /// - /// Locks the settings to prevent further modifications - /// - internal void PreventChanges() + void EnsureMergingIsPossible() { - locked = true; + if (locked) + { + throw new ConfigurationErrorsException("Unable to merge settings. The settings has been locked for modifications. Move any configuration code earlier in the configuration pipeline"); + } } void EnsureWriteEnabled(string key) { if (locked) { - throw new ConfigurationErrorsException(string.Format("Unable to set the value for key: {0}. The settings has been locked for modifications. Please move any configuration code earlier in the configuration pipeline", key)); + throw new ConfigurationErrorsException($"Unable to set the value for key: {key}. The settings has been locked for modifications. Move any configuration code earlier in the configuration pipeline"); } } - bool locked; - /// - /// Applies property inject for the given type based on convention + /// Clears the settings holder default values and overrides, if a value is disposable the dispose method will be called. /// - /// The type - /// - public void ApplyTo(IComponentConfig config) + public void Clear() { - var targetType = typeof(T); - - foreach (var property in targetType.GetProperties()) + foreach (var item in Defaults) { - var settingsKey = targetType.FullName + "." + property.Name; + (item.Value as IDisposable)?.Dispose(); + } - if (HasSetting(settingsKey)) - { - config.ConfigureProperty(property.Name, Get(settingsKey)); - } + Defaults.Clear(); + + foreach (var item in Overrides) + { + (item.Value as IDisposable)?.Dispose(); } + + Overrides.Clear(); } - readonly ConcurrentDictionary Overrides = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - readonly ConcurrentDictionary Defaults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + ConcurrentDictionary Defaults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + bool locked; + + ConcurrentDictionary Overrides = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Settings/TransactionSettings.cs b/src/NServiceBus.Core/Settings/TransactionSettings.cs deleted file mode 100644 index 241d64b2bc7..00000000000 --- a/src/NServiceBus.Core/Settings/TransactionSettings.cs +++ /dev/null @@ -1,140 +0,0 @@ -namespace NServiceBus.Settings -{ - using System; - using System.Configuration; - using System.Transactions; - using System.Transactions.Configuration; - using NServiceBus.Unicast.Transport; - - /// - /// Configuration class for Transaction settings. - /// - public class TransactionSettings - { - internal TransactionSettings(BusConfiguration config) - { - this.config = config; - maxTimeout = GetMaxTimeout(); - } - - /// - /// Configures the not to not use any transactions. - /// - public TransactionSettings Disable() - { - config.Settings.Set("Transactions.Enabled", false); - config.Settings.SetDefault("Transactions.DoNotWrapHandlersExecutionInATransactionScope", true); - config.Settings.SetDefault("Transactions.SuppressDistributedTransactions", true); - - return this; - } - - /// - /// Configures the to use transactions. - /// - public TransactionSettings Enable() - { - config.Settings.Set("Transactions.Enabled", true); - config.Settings.SetDefault("Transactions.DoNotWrapHandlersExecutionInATransactionScope", false); - config.Settings.SetDefault("Transactions.SuppressDistributedTransactions", false); - - return this; - } - - /// - /// Sets the isolation level of the transaction. - /// - /// - /// A enumeration that specifies the isolation level of the - /// transaction. - /// - public TransactionSettings IsolationLevel(IsolationLevel isolationLevel) - { - config.Settings.Set("Transactions.IsolationLevel", isolationLevel); - - return this; - } - - /// - /// Configures the not to enlist in Distributed Transactions. - /// - public TransactionSettings DisableDistributedTransactions() - { - config.Settings.Set("Transactions.SuppressDistributedTransactions", true); - config.Settings.SetDefault("Transactions.DoNotWrapHandlersExecutionInATransactionScope", true); - return this; - } - - /// - /// Configures the to enlist in Distributed Transactions. - /// - public TransactionSettings EnableDistributedTransactions() - { - config.Settings.Set("Transactions.SuppressDistributedTransactions", false); - return this; - } - - /// - /// Configures this endpoint so that handlers are not wrapped in a - /// . - /// - public TransactionSettings DoNotWrapHandlersExecutionInATransactionScope() - { - config.Settings.Set("Transactions.DoNotWrapHandlersExecutionInATransactionScope", true); - return this; - } - - /// - /// Configures this endpoint so that handlers not wrapped in a - /// . - /// - public TransactionSettings WrapHandlersExecutionInATransactionScope() - { - config.Settings.Set("Transactions.DoNotWrapHandlersExecutionInATransactionScope", false); - return this; - } - - /// - /// Sets the default timeout period for the transaction. - /// - /// - /// A value that specifies the default timeout period for the - /// transaction. - /// - public TransactionSettings DefaultTimeout(TimeSpan defaultTimeout) - { - if (defaultTimeout > maxTimeout) - { - throw new ConfigurationErrorsException( - "Timeout requested is longer than the maximum value for this machine. Please override using the maxTimeout setting of the system.transactions section in machine.config"); - } - - config.Settings.Set("Transactions.DefaultTimeout", defaultTimeout); - return this; - } - - static TimeSpan GetMaxTimeout() - { - //default is 10 always 10 minutes - var maxTimeout = TimeSpan.FromMinutes(10); - - var systemTransactionsGroup = ConfigurationManager.OpenMachineConfiguration() - .GetSectionGroup("system.transactions"); - - if (systemTransactionsGroup != null) - { - var machineSettings = systemTransactionsGroup.Sections.Get("machineSettings") as MachineSettingsSection; - - if (machineSettings != null) - { - maxTimeout = machineSettings.MaxTimeout; - } - } - - return maxTimeout; - } - - BusConfiguration config; - TimeSpan maxTimeout; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Settings/TransactionSettingsExtentions.cs b/src/NServiceBus.Core/Settings/TransactionSettingsExtentions.cs deleted file mode 100644 index d9728b412a1..00000000000 --- a/src/NServiceBus.Core/Settings/TransactionSettingsExtentions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus -{ - using Settings; - - /// - /// Provides a hook to allows users fine grained control over transactionality - /// - public static class TransactionSettingsExtentions - { - /// - /// Entry point for transaction related configuration - /// - /// instance. - public static TransactionSettings Transactions(this BusConfiguration config) - { - return new TransactionSettings(config); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/SettingsExtensions.cs b/src/NServiceBus.Core/SettingsExtensions.cs new file mode 100644 index 00000000000..206d681c268 --- /dev/null +++ b/src/NServiceBus.Core/SettingsExtensions.cs @@ -0,0 +1,100 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Config.ConfigurationSource; + using Settings; + + /// + /// Provides extensions to the settings holder. + /// + public static class SettingsExtensions + { + /// + /// Returns the requested config section using the current configuration source. + /// + public static T GetConfigSection(this ReadOnlySettings settings) where T : class, new() + { + Guard.AgainstNull(nameof(settings), settings); + var typesToScan = settings.GetAvailableTypes(); + var configurationSource = settings.Get(); + + // ReSharper disable HeapView.SlowDelegateCreation + var sectionOverrideType = typesToScan.Where(t => !t.IsAbstract) + .FirstOrDefault(t => typeof(IProvideConfiguration).IsAssignableFrom(t)); + // ReSharper restore HeapView.SlowDelegateCreation + + if (sectionOverrideType == null) + { + return configurationSource.GetConfiguration(); + } + + IProvideConfiguration sectionOverride; + + if (HasConstructorThatAcceptsSettings(sectionOverrideType)) + { + sectionOverride = (IProvideConfiguration) Activator.CreateInstance(sectionOverrideType, settings); + } + else + { + sectionOverride = (IProvideConfiguration) Activator.CreateInstance(sectionOverrideType); + } + + return sectionOverride.GetConfiguration(); + } + + /// + /// Gets the list of types available to this endpoint. + /// + public static IList GetAvailableTypes(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + return settings.Get>("TypesToScan"); + } + + /// + /// Returns the name of this endpoint. + /// + public static string EndpointName(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + return settings.Get("NServiceBus.Routing.EndpointName"); + } + + /// + /// Returns the logical address of this endpoint. + /// + public static LogicalAddress LogicalAddress(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + return settings.Get(); + } + + /// + /// Returns the shared queue name of this endpoint. + /// + public static string LocalAddress(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + return settings.Get("NServiceBus.SharedQueue"); + } + + /// + /// Returns the instance-specific queue name of this endpoint. + /// + public static string InstanceSpecificQueue(this ReadOnlySettings settings) + { + Guard.AgainstNull(nameof(settings), settings); + return settings.GetOrDefault("NServiceBus.EndpointSpecificQueue"); + } + + static bool HasConstructorThatAcceptsSettings(Type sectionOverrideType) + { + return sectionOverrideType.GetConstructor(new[] + { + typeof(ReadOnlySettings) + }) != null; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/SettingsExtentions.cs b/src/NServiceBus.Core/SettingsExtentions.cs deleted file mode 100644 index e042ecf89a9..00000000000 --- a/src/NServiceBus.Core/SettingsExtentions.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Config.ConfigurationSource; - using Settings; - - /// - /// Provides extentions to the settings holder - /// - public static class SettingsExtentions - { - /// - /// Returns the requested config section using the current configuration source. - /// - public static T GetConfigSection(this ReadOnlySettings settings) where T : class, new() - { - var typesToScan = settings.GetAvailableTypes(); - var configurationSource = settings.Get(); - - // ReSharper disable HeapView.SlowDelegateCreation - var sectionOverrideType = typesToScan.FirstOrDefault(t => typeof(IProvideConfiguration).IsAssignableFrom(t)); - // ReSharper restore HeapView.SlowDelegateCreation - - if (sectionOverrideType == null) - { - return configurationSource.GetConfiguration(); - } - - IProvideConfiguration sectionOverride; - - if (HasConstructorThatAcceptsSettings(sectionOverrideType)) - { - sectionOverride = (IProvideConfiguration)Activator.CreateInstance(sectionOverrideType, new object[] { settings }); - } - else - { - sectionOverride = (IProvideConfiguration)Activator.CreateInstance(sectionOverrideType); - } - - return sectionOverride.GetConfiguration(); - } - - /// - /// Gets the list of types available to this endpoint. - /// - public static IList GetAvailableTypes(this ReadOnlySettings settings) - { - return settings.Get>("TypesToScan"); - } - - /// - /// Returns the name of this endpoint. - /// - public static string EndpointName(this ReadOnlySettings settings) - { - return settings.Get("EndpointName"); - } - - /// - /// Returns the queue name of this endpoint. - /// - public static Address LocalAddress(this ReadOnlySettings settings) - { - return Address.Parse(settings.Get("NServiceBus.LocalAddress")); - } - - static bool HasConstructorThatAcceptsSettings(Type sectionOverrideType) - { - return sectionOverrideType.GetConstructor(new [] { typeof(ReadOnlySettings) }) != null; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/StartableEndpoint.cs b/src/NServiceBus.Core/StartableEndpoint.cs new file mode 100644 index 00000000000..b6582505f93 --- /dev/null +++ b/src/NServiceBus.Core/StartableEndpoint.cs @@ -0,0 +1,190 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Security.Principal; + using System.Threading.Tasks; + using Config; + using ConsistencyGuarantees; + using Features; + using Logging; + using ObjectBuilder; + using Pipeline; + using Settings; + using Transport; + + class StartableEndpoint : IStartableEndpoint + { + public StartableEndpoint(SettingsHolder settings, IBuilder builder, FeatureActivator featureActivator, PipelineConfiguration pipelineConfiguration, IEventAggregator eventAggregator, TransportInfrastructure transportInfrastructure, CriticalError criticalError) + { + this.criticalError = criticalError; + this.settings = settings; + this.builder = builder; + this.featureActivator = featureActivator; + this.pipelineConfiguration = pipelineConfiguration; + this.eventAggregator = eventAggregator; + this.transportInfrastructure = transportInfrastructure; + + pipelineCache = new PipelineCache(builder, settings); + + messageSession = new MessageSession(new RootContext(builder, pipelineCache, eventAggregator)); + } + + public async Task Start() + { + DetectThrottlingConfig(); + + await transportInfrastructure.Start().ConfigureAwait(false); + + AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); + + var mainPipeline = new Pipeline(builder, settings, pipelineConfiguration.Modifications); + var receivers = CreateReceivers(mainPipeline); + await InitializeReceivers(receivers).ConfigureAwait(false); + + var featureRunner = await StartFeatures(messageSession).ConfigureAwait(false); + + var runningInstance = new RunningEndpointInstance(settings, builder, receivers, featureRunner, messageSession, transportInfrastructure); + // set the started endpoint on CriticalError to pass the endpoint to the critical error action + criticalError.SetEndpoint(runningInstance); + + StartReceivers(receivers); + + return runningInstance; + } + + async Task StartFeatures(IMessageSession session) + { + var featureRunner = new FeatureRunner(featureActivator); + await featureRunner.Start(builder, session).ConfigureAwait(false); + return featureRunner; + } + + static async Task InitializeReceivers(List receivers) + { + foreach (var receiver in receivers) + { + try + { + await receiver.Init().ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.Fatal($"Receiver {receiver.Id} failed to initialize.", ex); + throw; + } + } + } + + static void StartReceivers(List receivers) + { + foreach (var receiver in receivers) + { + try + { + receiver.Start(); + } + catch (Exception ex) + { + Logger.Fatal($"Receiver {receiver.Id} failed to start.", ex); + throw; + } + } + } + + List CreateReceivers(IPipeline mainPipeline) + { + if (settings.GetOrDefault("Endpoint.SendOnly")) + { + return new List(); + } + + var purgeOnStartup = settings.GetOrDefault("Transport.PurgeOnStartup"); + var errorQueue = settings.ErrorQueueAddress(); + var dequeueLimitations = GeDequeueLimitationsForReceivePipeline(); + var requiredTransactionSupport = settings.GetRequiredTransactionModeForReceives(); + var recoverabilityExecutorFactory = builder.Build(); + + var receivers = BuildMainReceivers(errorQueue, purgeOnStartup, requiredTransactionSupport, dequeueLimitations, recoverabilityExecutorFactory, mainPipeline); + + foreach (var satellitePipeline in settings.Get().Definitions) + { + var satelliteRecoverabilityExecutor = recoverabilityExecutorFactory.Create(satellitePipeline.RecoverabilityPolicy, eventAggregator, satellitePipeline.ReceiveAddress); + var satellitePushSettings = new PushSettings(satellitePipeline.ReceiveAddress, errorQueue, purgeOnStartup, satellitePipeline.RequiredTransportTransactionMode); + + receivers.Add(new TransportReceiver(satellitePipeline.Name, builder.Build(), satellitePushSettings, dequeueLimitations, new SatellitePipelineExecutor(builder, satellitePipeline), satelliteRecoverabilityExecutor, criticalError)); + } + + return receivers; + } + + List BuildMainReceivers(string errorQueue, bool purgeOnStartup, TransportTransactionMode requiredTransactionSupport, PushRuntimeSettings dequeueLimitations, RecoverabilityExecutorFactory recoverabilityExecutorFactory, IPipeline mainPipeline) + { + var localAddress = settings.LocalAddress(); + var recoverabilityExecutor = recoverabilityExecutorFactory.CreateDefault(eventAggregator, localAddress); + var pushSettings = new PushSettings(settings.LocalAddress(), errorQueue, purgeOnStartup, requiredTransactionSupport); + var mainPipelineExecutor = new MainPipelineExecutor(builder, eventAggregator, pipelineCache, mainPipeline); + + var receivers = new List(); + + receivers.Add(new TransportReceiver(MainReceiverId, builder.Build(), pushSettings, dequeueLimitations, mainPipelineExecutor, recoverabilityExecutor, criticalError)); + + if (settings.InstanceSpecificQueue() != null) + { + var instanceSpecificQueue = settings.InstanceSpecificQueue(); + var instanceSpecificRecoverabilityExecutor = recoverabilityExecutorFactory.CreateDefault(eventAggregator, instanceSpecificQueue); + var sharedReceiverPushSettings = new PushSettings(settings.InstanceSpecificQueue(), errorQueue, purgeOnStartup, requiredTransactionSupport); + + receivers.Add(new TransportReceiver(MainReceiverId, builder.Build(), sharedReceiverPushSettings, dequeueLimitations, mainPipelineExecutor, instanceSpecificRecoverabilityExecutor, criticalError)); + } + + return receivers; + } + + //note: this should be handled in a feature but we don't have a good + // extension point to plugin atm + PushRuntimeSettings GeDequeueLimitationsForReceivePipeline() + { + var transportConfig = settings.GetConfigSection(); + + if (transportConfig != null && transportConfig.MaximumConcurrencyLevel != 0) + { + throw new NotSupportedException($"The TransportConfig.MaximumConcurrencyLevel has been removed. Remove the '{nameof(TransportConfig.MaximumMessageThroughputPerSecond)}' attribute from the '{nameof(TransportConfig)}' configuration section and use 'EndpointConfiguration.LimitMessageProcessingConcurrencyTo' instead."); + } + + MessageProcessingOptimizationExtensions.ConcurrencyLimit concurrencyLimit; + + if (settings.TryGet(out concurrencyLimit)) + { + return new PushRuntimeSettings(concurrencyLimit.MaxValue); + } + + return PushRuntimeSettings.Default; + } + + [ObsoleteEx(Message = "Not needed anymore", RemoveInVersion = "7.0")] + void DetectThrottlingConfig() + { + var throughputConfiguration = settings.GetConfigSection()?.MaximumMessageThroughputPerSecond; + if (throughputConfiguration.HasValue && throughputConfiguration != -1) + { + throw new NotSupportedException($"Message throughput throttling has been removed. Remove the '{nameof(TransportConfig.MaximumMessageThroughputPerSecond)}' attribute from the '{nameof(TransportConfig)}' configuration section and consult the documentation for further information."); + } + } + + IMessageSession messageSession; + IBuilder builder; + FeatureActivator featureActivator; + + IPipelineCache pipelineCache; + PipelineConfiguration pipelineConfiguration; + + SettingsHolder settings; + IEventAggregator eventAggregator; + TransportInfrastructure transportInfrastructure; + CriticalError criticalError; + + const string MainReceiverId = "Main"; + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/StaticHeaders/ApplyStaticHeadersBehavior.cs b/src/NServiceBus.Core/StaticHeaders/ApplyStaticHeadersBehavior.cs new file mode 100644 index 00000000000..0292624c9c9 --- /dev/null +++ b/src/NServiceBus.Core/StaticHeaders/ApplyStaticHeadersBehavior.cs @@ -0,0 +1,26 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Pipeline; + + class ApplyStaticHeadersBehavior : IBehavior + { + public ApplyStaticHeadersBehavior(CurrentStaticHeaders currentStaticHeaders) + { + this.currentStaticHeaders = currentStaticHeaders; + } + + public Task Invoke(IOutgoingLogicalMessageContext context, Func next) + { + foreach (var staticHeader in currentStaticHeaders) + { + context.Headers[staticHeader.Key] = staticHeader.Value; + } + + return next(context); + } + + CurrentStaticHeaders currentStaticHeaders; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/StaticHeaders/CurrentStaticHeaders.cs b/src/NServiceBus.Core/StaticHeaders/CurrentStaticHeaders.cs new file mode 100644 index 00000000000..e9417c79c14 --- /dev/null +++ b/src/NServiceBus.Core/StaticHeaders/CurrentStaticHeaders.cs @@ -0,0 +1,8 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + + class CurrentStaticHeaders : Dictionary + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/StaticHeaders/StaticHeaders.cs b/src/NServiceBus.Core/StaticHeaders/StaticHeaders.cs new file mode 100644 index 00000000000..bafc41c2fd1 --- /dev/null +++ b/src/NServiceBus.Core/StaticHeaders/StaticHeaders.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using Features; + + class StaticHeaders : Feature + { + public StaticHeaders() + { + EnableByDefault(); + Prerequisite(c => c.Settings.HasSetting(), "No static outgoing headers registered"); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var headers = context.Settings.Get(); + context.Pipeline.Register("ApplyStaticHeaders", new ApplyStaticHeadersBehavior(headers), "Applies static headers to outgoing messages"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/StaticHeaders/StaticHeadersConfigExtensions.cs b/src/NServiceBus.Core/StaticHeaders/StaticHeadersConfigExtensions.cs new file mode 100644 index 00000000000..31187accbf5 --- /dev/null +++ b/src/NServiceBus.Core/StaticHeaders/StaticHeadersConfigExtensions.cs @@ -0,0 +1,34 @@ +namespace NServiceBus +{ + /// + /// Extensions to the public configuration api. + /// + public static class StaticHeadersConfigExtensions + { + /// + /// Adds a header that will be attached to all outgoing messages for this endpoint. These headers can not be changed at + /// runtime. Use a outgoing message mutator + /// if you need to apply headers that needs to be dynamic per message. You can also set headers explicitly for a given + /// message using any of the Send/Reply or PublishOptions. + /// + /// The instance to apply the settings to. + /// The static header key. + /// The static header value. + public static void AddHeaderToAllOutgoingMessages(this EndpointConfiguration config, string key, string value) + { + Guard.AgainstNullAndEmpty(nameof(key), key); + + CurrentStaticHeaders headers; + + if (!config.Settings.TryGet(out headers)) + { + headers = new CurrentStaticHeaders(); + + config.Settings.Set(headers); + } + + + headers[key] = value; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Support/RuntimeEnvironment.cs b/src/NServiceBus.Core/Support/RuntimeEnvironment.cs index 1efd65dc00f..56ce4b0228c 100644 --- a/src/NServiceBus.Core/Support/RuntimeEnvironment.cs +++ b/src/NServiceBus.Core/Support/RuntimeEnvironment.cs @@ -3,26 +3,25 @@ using System; /// - /// Abstracts the runtime environment + /// Abstracts the runtime environment. /// public static class RuntimeEnvironment { static RuntimeEnvironment() { - MachineNameAction = () => Environment.MachineName; + var machineName = Environment.MachineName; + + MachineNameAction = () => machineName; } /// - /// Returns the machine name where this endpoint is currently running + /// Returns the machine name where this endpoint is currently running. /// - public static string MachineName - { - get { return MachineNameAction(); } - } + public static string MachineName => MachineNameAction(); /// - /// Get the machine name, allows for overrides + /// Get the machine name, allows for overrides. /// public static Func MachineNameAction { get; set; } } diff --git a/src/NServiceBus.Core/TaskEx.cs b/src/NServiceBus.Core/TaskEx.cs new file mode 100644 index 00000000000..8c1482a21f7 --- /dev/null +++ b/src/NServiceBus.Core/TaskEx.cs @@ -0,0 +1,46 @@ +namespace NServiceBus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + static class TaskEx + { + const string TaskIsNullExceptionMessage = "Return a Task or mark the method as async."; + + // ReSharper disable once UnusedParameter.Global + // Used to explicitly suppress the compiler warning about + // using the returned value from async operations + public static void Ignore(this Task task) + { + } + + //TODO: remove when we update to 4.6 and can use Task.CompletedTask + public static readonly Task CompletedTask = Task.FromResult(0); + + public static readonly Task TrueTask = Task.FromResult(true); + public static readonly Task FalseTask = Task.FromResult(false); + + public static Task ThrowIfNull(this Task task) + { + if (task != null) + { + return task; + } + + throw new Exception(TaskIsNullExceptionMessage); + } + + public static Task ThrowIfNull(this Task task) + { + if (task != null) + { + return task; + } + + throw new Exception(TaskIsNullExceptionMessage); + } + + public static Task Run(Func func, object state) => Task.Factory.StartNew(func, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/TimeToBeReceivedAttribute.cs b/src/NServiceBus.Core/TimeToBeReceivedAttribute.cs deleted file mode 100644 index 587a012eb36..00000000000 --- a/src/NServiceBus.Core/TimeToBeReceivedAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Attribute to indicate that a message has a period of time in which to be received. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] - public sealed class TimeToBeReceivedAttribute : Attribute - { - - /// - /// Sets the time to be received. - /// - /// A timeSpan that can be interpreted by . - public TimeToBeReceivedAttribute(string timeSpan) - { - TimeToBeReceived = TimeSpan.Parse(timeSpan); - } - - /// - /// Gets the maximum time in which a message must be received. - /// - /// - /// If the interval specified by the property expires before the message - /// is received by the destination of the message the message will automatically be canceled. - /// - public TimeSpan TimeToBeReceived { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/ConfigurationBuilderExtensions.cs b/src/NServiceBus.Core/Timeout/ConfigurationBuilderExtensions.cs deleted file mode 100644 index e20a91aa967..00000000000 --- a/src/NServiceBus.Core/Timeout/ConfigurationBuilderExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Extension methods declarations. - /// - public static class ConfigurationTimeoutExtensions - { - /// - /// A critical error is raised when timeout retrieval fails. - /// By default we wait for 2 seconds for the storage to come back. - /// This method allows to change the default and extend the wait time. - /// - /// - /// Time to wait before raising a critical error. - public static void TimeToWaitBeforeTriggeringCriticalErrorOnTimeoutOutages(this BusConfiguration config, TimeSpan timeToWait) - { - config.Settings.Set("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver", timeToWait); - } - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/DefaultTimeoutManager.cs b/src/NServiceBus.Core/Timeout/Core/DefaultTimeoutManager.cs deleted file mode 100644 index 2eb296f4509..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/DefaultTimeoutManager.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - using System; - using Transports; - - class DefaultTimeoutManager - { - public IPersistTimeouts TimeoutsPersister { get; set; } - - public ISendMessages MessageSender { get; set; } - - public Configure Configure { get; set; } - - public Action TimeoutPushed; - - public void PushTimeout(TimeoutData timeout) - { - if (timeout.Time.AddSeconds(-1) <= DateTime.UtcNow) - { - MessageSender.Send(timeout.ToTransportMessage(), timeout.ToSendOptions(Configure.LocalAddress)); - return; - } - - TimeoutsPersister.Add(timeout); - - if (TimeoutPushed != null) - { - TimeoutPushed(timeout); - } - } - - public void RemoveTimeout(string timeoutId) - { - TimeoutData timeoutData; - - TimeoutsPersister.TryRemove(timeoutId, out timeoutData); - } - - public void RemoveTimeoutBy(Guid sagaId) - { - TimeoutsPersister.RemoveTimeoutBy(sagaId); - } - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/IPersistTimeouts.cs b/src/NServiceBus.Core/Timeout/Core/IPersistTimeouts.cs deleted file mode 100644 index 67250c30aa4..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/IPersistTimeouts.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - using System; - using System.Collections.Generic; - - /// - /// Timeout persister contract. - /// - public interface IPersistTimeouts - { - /// - /// Retrieves the next range of timeouts that are due. - /// - /// The time where to start retrieving the next slice, the slice should exclude this date. - /// Returns the next time we should query again. - /// Returns the next range of timeouts that are due. - IEnumerable> GetNextChunk(DateTime startSlice, out DateTime nextTimeToRunQuery); - - /// - /// Adds a new timeout. - /// - /// Timeout data. - void Add(TimeoutData timeout); - - /// - /// Removes the timeout if it hasn't been previously removed. - /// - /// The timeout id to remove. - /// The timeout data of the removed timeout. - /// true it the timeout was successfully removed. - bool TryRemove(string timeoutId, out TimeoutData timeoutData); - - /// - /// Removes the time by saga id. - /// - /// The saga id of the timeouts to remove. - void RemoveTimeoutBy(Guid sagaId); - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/IPersistTimeoutsV2.cs b/src/NServiceBus.Core/Timeout/Core/IPersistTimeoutsV2.cs deleted file mode 100644 index e02088492a6..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/IPersistTimeoutsV2.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - /// - /// Timeout persister contract. - /// - public interface IPersistTimeoutsV2 - { - /// - /// Reads timeout data. - /// - /// The timeout id to read. - /// of the timeout if it was found. null otherwise. - TimeoutData Peek(string timeoutId); - - /// - /// Removes the timeout if it hasn't been previously removed. - /// - /// The timeout id to remove. - /// true if the timeout has been successfully removed or false if there was no timeout to remove. - bool TryRemove(string timeoutId); - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/TimeoutData.cs b/src/NServiceBus.Core/Timeout/Core/TimeoutData.cs deleted file mode 100644 index 2084d7b3a83..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/TimeoutData.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - using System; - using System.Collections.Generic; - using Unicast; - - /// - /// Holds timeout information. - /// - public class TimeoutData - { - /// - /// Id of this timeout - /// - public string Id { get; set; } - - /// - /// The address of the client who requested the timeout. - /// - public Address Destination { get; set; } - - /// - /// The saga ID. - /// - public Guid SagaId { get; set; } - - /// - /// Additional state. - /// - public byte[] State { get; set; } - - /// - /// The time at which the timeout expires. - /// - public DateTime Time { get; set; } - - /// - /// The timeout manager that owns this particular timeout - /// - public string OwningTimeoutManager { get; set; } - - /// - /// Store the headers to preserve them across timeouts - /// - public Dictionary Headers { get; set; } - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - /// 2 - public override string ToString() - { - return string.Format("Timeout({0}) - Expires:{1}, SagaId:{2}", Id, Time, SagaId); - } - - /// - /// Transforms the timeout to a . - /// - /// Returns a . - public TransportMessage ToTransportMessage() - { - var transportMessage = new TransportMessage(Id,Headers) - { - Recoverable = true, - Body = State - }; - - - if (SagaId != Guid.Empty) - { - transportMessage.Headers[NServiceBus.Headers.SagaId] = SagaId.ToString(); - } - - transportMessage.Headers[NServiceBus.Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); - transportMessage.Headers["NServiceBus.RelatedToTimeoutId"] = Id; - - return transportMessage; - } - - /// - /// Transforms the timeout to send options. - /// - /// The reply address to use for outgoing messages - public SendOptions ToSendOptions(Address replyToAddress) - { - if (Headers != null) - { - string originalReplyToAddressValue; - if (Headers.TryGetValue(OriginalReplyToAddress, out originalReplyToAddressValue)) - { - replyToAddress = Address.Parse(originalReplyToAddressValue); - Headers.Remove(OriginalReplyToAddress); - } - } - - return new SendOptions(Destination) - { - ReplyToAddress = replyToAddress - }; - } - - /// - /// Original ReplyTo address header. - /// - public const string OriginalReplyToAddress = "NServiceBus.Timeout.ReplyToAddress"; - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/TimeoutManagerDeferrer.cs b/src/NServiceBus.Core/Timeout/Core/TimeoutManagerDeferrer.cs deleted file mode 100644 index de2b253f588..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/TimeoutManagerDeferrer.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace NServiceBus.Timeout -{ - using System; - using Logging; - using Transports; - using Unicast; - using Unicast.Transport; - - class TimeoutManagerDeferrer : IDeferMessages - { - public ISendMessages MessageSender { get; set; } - public Address TimeoutManagerAddress { get; set; } - public Configure Configure { get; set; } - - public void Defer(TransportMessage message, SendOptions sendOptions) - { - message.Headers[TimeoutManagerHeaders.RouteExpiredTimeoutTo] = sendOptions.Destination.ToString(); - - DateTime deliverAt; - - if (sendOptions.DelayDeliveryWith.HasValue) - { - deliverAt = DateTime.UtcNow + sendOptions.DelayDeliveryWith.Value; - } - else - { - if (sendOptions.DeliverAt.HasValue) - { - deliverAt = sendOptions.DeliverAt.Value; - } - else - { - throw new ArgumentException("A delivery time needs to be specified for Deferred messages"); - } - - } - - message.Headers[TimeoutManagerHeaders.Expire] = DateTimeExtensions.ToWireFormattedString(deliverAt); - - try - { - MessageSender.Send(message, new SendOptions(TimeoutManagerAddress)); - } - catch (Exception ex) - { - Log.Error("There was a problem deferring the message. Make sure that DisableTimeoutManager was not called for your endpoint.", ex); - throw; - } - } - - public void ClearDeferredMessages(string headerKey, string headerValue) - { - var controlMessage = ControlMessage.Create(); - - controlMessage.Headers[headerKey] = headerValue; - controlMessage.Headers[TimeoutManagerHeaders.ClearTimeouts] = Boolean.TrueString; - - MessageSender.Send(controlMessage, new SendOptions(TimeoutManagerAddress) { ReplyToAddress = Configure.PublicReturnAddress }); - } - - static ILog Log = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheck.cs b/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheck.cs deleted file mode 100644 index 9fe35ceac27..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheck.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - using NServiceBus.Config; - using System; - using System.Configuration; - using NServiceBus.ObjectBuilder; - using NServiceBus.Settings; - using NServiceBus.Transports; - - class TimeoutPersistenceVersionCheck : IWantToRunWhenConfigurationIsComplete - { - internal const string SuppressOutdatedTimeoutPersistenceWarning = "NServiceBus/suppress-outdated-timeout-persistence-warning"; - - readonly IBuilder builder; - internal SettingsHolder settingsHolder; - - public TimeoutPersistenceVersionCheck(IBuilder builder) - { - this.builder = builder; - } - - public void Run(Configure config) - { - settingsHolder = config.Settings; - var suppressDtc = settingsHolder.Get("Transactions.SuppressDistributedTransactions"); - if (IsTransportSupportingDtc() && !suppressDtc) - { - // there is no issue with the timeout persistence when using dtc - return; - } - - var timeoutPersister = TryResolveTimeoutPersister(); - if (timeoutPersister == null) - { - // no timeouts used - return; - } - - if (!(timeoutPersister is IPersistTimeoutsV2) && !UserSuppressedTimeoutPersistenceWarning()) - { - throw new Exception("You are using an outdated timeout persistence which can lead to message loss! Please update the configured timeout persistence. You can suppress this warning by configuring your bus using 'config.SuppressOutdatedTimeoutPersistenceWarning()' or by adding 'NServiceBus/suppress-outdated-timeout-persistence-warning' with value 'true' to the appSettings section of your application configuration file."); - } - } - - IPersistTimeouts TryResolveTimeoutPersister() - { - IPersistTimeouts timeoutPersister = null; - try - { - timeoutPersister = builder.Build(); - } - catch (Exception) - { - // catch potential DI exception when interface not registered. - } - - return timeoutPersister; - } - - bool UserSuppressedTimeoutPersistenceWarning() - { - if (settingsHolder.HasSetting(SuppressOutdatedTimeoutPersistenceWarning)) - { - return settingsHolder.GetOrDefault(SuppressOutdatedTimeoutPersistenceWarning); - } - - var appSetting = ConfigurationManager.AppSettings[SuppressOutdatedTimeoutPersistenceWarning]; - if (appSetting != null) - { - return bool.Parse(appSetting); - } - - return false; - } - - bool IsTransportSupportingDtc() - { - var selectedTransport = settingsHolder.GetOrDefault("NServiceBus.Transports.TransportDefinition"); - if (selectedTransport.HasSupportForDistributedTransactions.HasValue) - { - return selectedTransport.HasSupportForDistributedTransactions.Value; - } - - return !selectedTransport.GetType().Name.Contains("RabbitMQ"); - } - } -} diff --git a/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheckExtension.cs b/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheckExtension.cs deleted file mode 100644 index 90f04fe937b..00000000000 --- a/src/NServiceBus.Core/Timeout/Core/TimeoutPersistenceVersionCheckExtension.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Timeout.Core -{ - /// - /// Provides methods for suppressing startup checks regarding the selected TimeoutPersistance. - /// - public static class TimeoutPersistenceVersionCheckExtension - { - /// - /// Suppresses warning if selected TimeoutPersistance doesn't contain the hotfix preventing potential message loss. - /// - /// The instance. - /// - public static BusConfiguration SuppressOutdatedTimeoutPersistenceWarning(this BusConfiguration configure) - { - configure.Settings.Set(TimeoutPersistenceVersionCheck.SuppressOutdatedTimeoutPersistenceWarning, true); - return configure; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/Hosting/Windows/ManageMessageFailuresWithoutSlr.cs b/src/NServiceBus.Core/Timeout/Hosting/Windows/ManageMessageFailuresWithoutSlr.cs deleted file mode 100644 index e0e1a254c33..00000000000 --- a/src/NServiceBus.Core/Timeout/Hosting/Windows/ManageMessageFailuresWithoutSlr.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace NServiceBus.Timeout.Hosting.Windows -{ - using System; - using Faults; - using Logging; - using Transports; - using Unicast; - using Unicast.Queuing; - - class ManageMessageFailuresWithoutSlr : IManageMessageFailures - { - ISendMessages messageSender; - readonly Configure config; - static ILog Logger = LogManager.GetLogger(); - - Address localAddress; - Address errorQueue; - - public ManageMessageFailuresWithoutSlr(IManageMessageFailures mainFailureManager, ISendMessages messageSender, Configure config) - { - this.messageSender = messageSender; - this.config = config; - var mainTransportFailureManager = mainFailureManager as Faults.Forwarder.FaultManager; - if (mainTransportFailureManager != null) - { - errorQueue = mainTransportFailureManager.ErrorQueue; - } - } - - public void SerializationFailedForMessage(TransportMessage message, Exception e) - { - SendFailureMessage(message, e, "SerializationFailed"); - } - - public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e) - { - SendFailureMessage(message, e, "ProcessingFailed"); - } - - void SendFailureMessage(TransportMessage message, Exception e, string reason) - { - if (errorQueue == null) - { - Logger.Error("Message processing always fails for message with ID " + message.Id + ".", e); - return; - } - - message.SetExceptionHeaders(e, localAddress ?? config.LocalAddress,reason); - - try - { - messageSender.Send(message, new SendOptions(errorQueue)); - } - catch (QueueNotFoundException exception) - { - var errorMessage = string.Format("Could not forward failed message to error queue '{0}' as it could not be found.", exception.Queue); - Logger.Fatal(errorMessage); - throw new InvalidOperationException(errorMessage, exception); - } - catch (Exception exception) - { - var errorMessage = "Could not forward failed message to error queue."; - Logger.Fatal(errorMessage, exception); - throw new InvalidOperationException(errorMessage, exception); - } - } - - public void Init(Address address) - { - localAddress = address; - } - - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutDispatcherProcessor.cs b/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutDispatcherProcessor.cs deleted file mode 100644 index 0ac5d38d0c5..00000000000 --- a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutDispatcherProcessor.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace NServiceBus.Timeout.Hosting.Windows -{ - using System; - using Core; - using NServiceBus.Settings; - using Satellites; - using Transports; - using Unicast.Transport; - - class TimeoutDispatcherProcessor : IAdvancedSatellite - { - public TimeoutDispatcherProcessor() - { - Disabled = true; - } - - public ISendMessages MessageSender { get; set; } - - public IPersistTimeouts TimeoutsPersister { get; set; } - - public TimeoutPersisterReceiver TimeoutPersisterReceiver { get; set; } - - public Configure Configure { get; set; } - - public Address InputAddress { get; set; } - - public ReadOnlySettings Settings { get; set; } - - public bool Disabled { get; set; } - - public bool Handle(TransportMessage message) - { - var timeoutId = message.Headers["Timeout.Id"]; - - var persisterV2 = TimeoutsPersister as IPersistTimeoutsV2; - if (persisterV2 != null) - { - var timeoutData = persisterV2.Peek(timeoutId); - if (timeoutData == null) - { - return true; - } - - var sendOptions = timeoutData.ToSendOptions(Configure.LocalAddress); - - if (ShouldSuppressTransaction()) - { - sendOptions.EnlistInReceiveTransaction = false; - } - - MessageSender.Send(timeoutData.ToTransportMessage(), sendOptions); - - return persisterV2.TryRemove(timeoutId); - } - else - { - TimeoutData timeoutData; - if (TimeoutsPersister.TryRemove(timeoutId, out timeoutData)) - { - MessageSender.Send(timeoutData.ToTransportMessage(), timeoutData.ToSendOptions(Configure.LocalAddress)); - } - } - - return true; - } - - public void Start() - { - TimeoutPersisterReceiver.Start(); - } - - public void Stop() - { - TimeoutPersisterReceiver.Stop(); - } - - public Action GetReceiverCustomization() - { - return receiver => - { - //TODO: The line below needs to change when we refactor the slr to be: - // transport.DisableSLR() or similar - receiver.FailureManager = new ManageMessageFailuresWithoutSlr(receiver.FailureManager, MessageSender, Configure); - }; - } - - bool ShouldSuppressTransaction() - { - var suppressDtc = Settings.Get("Transactions.SuppressDistributedTransactions"); - return !IsTransportSupportingDtc() || suppressDtc; - } - - bool IsTransportSupportingDtc() - { - var selectedTransport = Settings.GetOrDefault("NServiceBus.Transports.TransportDefinition"); - if (selectedTransport.HasSupportForDistributedTransactions.HasValue) - { - return selectedTransport.HasSupportForDistributedTransactions.Value; - } - - return !selectedTransport.GetType().Name.Contains("RabbitMQ"); - } - } -} diff --git a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutMessageProcessor.cs b/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutMessageProcessor.cs deleted file mode 100644 index 0016e00402d..00000000000 --- a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutMessageProcessor.cs +++ /dev/null @@ -1,139 +0,0 @@ -namespace NServiceBus.Timeout.Hosting.Windows -{ - using System; - using Core; - using Satellites; - using Transports; - using Unicast; - using Unicast.Transport; - - class TimeoutMessageProcessor : IAdvancedSatellite - { - public TimeoutMessageProcessor() - { - Disabled = true; - } - - public ISendMessages MessageSender { get; set; } - - public Address InputAddress { get; set; } - - public DefaultTimeoutManager TimeoutManager { get; set; } - - public Configure Configure { get; set; } - - public bool Disabled { get; set; } - public string EndpointName{ get; set; } - - public void Start() - { - - } - - public void Stop() - { - - } - - public Action GetReceiverCustomization() - { - return receiver => - { - //TODO: The line below needs to change when we refactor the slr to be: - // transport.DisableSLR() or similar - receiver.FailureManager = new ManageMessageFailuresWithoutSlr(receiver.FailureManager, MessageSender, Configure); - }; - } - - public bool Handle(TransportMessage message) - { - //dispatch request will arrive at the same input so we need to make sure to call the correct handler - if (message.Headers.ContainsKey(TimeoutIdToDispatchHeader)) - { - HandleBackwardsCompatibility(message); - } - else - { - HandleInternal(message); - } - - return true; - } - - void HandleBackwardsCompatibility(TransportMessage message) - { - var timeoutId = message.Headers[TimeoutIdToDispatchHeader]; - - var destination = Address.Parse(message.Headers[TimeoutDestinationHeader]); - - //clear headers - message.Headers.Remove(TimeoutIdToDispatchHeader); - message.Headers.Remove(TimeoutDestinationHeader); - - string routeExpiredTimeoutTo; - if (message.Headers.TryGetValue(TimeoutManagerHeaders.RouteExpiredTimeoutTo, out routeExpiredTimeoutTo)) - { - destination = Address.Parse(routeExpiredTimeoutTo); - } - - MessageSender.Send(message, new SendOptions(destination)); - TimeoutManager.RemoveTimeout(timeoutId); - } - - void HandleInternal(TransportMessage message) - { - var sagaId = Guid.Empty; - - string sagaIdString; - if (message.Headers.TryGetValue(Headers.SagaId, out sagaIdString)) - { - sagaId = Guid.Parse(sagaIdString); - } - - if (message.Headers.ContainsKey(TimeoutManagerHeaders.ClearTimeouts)) - { - if (sagaId == Guid.Empty) - throw new InvalidOperationException("Invalid saga id specified, clear timeouts is only supported for saga instances"); - - TimeoutManager.RemoveTimeoutBy(sagaId); - } - else - { - string expire; - if (!message.Headers.TryGetValue(TimeoutManagerHeaders.Expire, out expire)) - { - throw new InvalidOperationException("Non timeout message arrived at the timeout manager, id:" + message.Id); - } - - var destination = message.ReplyToAddress; - - string routeExpiredTimeoutTo; - if (message.Headers.TryGetValue(TimeoutManagerHeaders.RouteExpiredTimeoutTo, out routeExpiredTimeoutTo)) - { - destination = Address.Parse(routeExpiredTimeoutTo); - } - - var data = new TimeoutData - { - Destination = destination, - SagaId = sagaId, - State = message.Body, - Time = DateTimeExtensions.ToUtcDateTime(expire), - Headers = message.Headers, - OwningTimeoutManager = EndpointName - }; - - //add a temp header so that we can make sure to restore the ReplyToAddress - if (message.ReplyToAddress != null) - { - data.Headers[TimeoutData.OriginalReplyToAddress] = message.ReplyToAddress.ToString(); - } - - TimeoutManager.PushTimeout(data); - } - } - - const string TimeoutDestinationHeader = "NServiceBus.Timeout.Destination"; - const string TimeoutIdToDispatchHeader = "NServiceBus.Timeout.TimeoutIdToDispatch"; - } -} diff --git a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutPersisterReceiver.cs b/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutPersisterReceiver.cs deleted file mode 100644 index 6fe2ff23ac9..00000000000 --- a/src/NServiceBus.Core/Timeout/Hosting/Windows/TimeoutPersisterReceiver.cs +++ /dev/null @@ -1,167 +0,0 @@ -namespace NServiceBus.Timeout.Hosting.Windows -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using CircuitBreakers; - using Core; - using Logging; - using Transports; - using Unicast; - using Unicast.Transport; - - class TimeoutPersisterReceiver : IDisposable - { - public IPersistTimeouts TimeoutsPersister { get; set; } - public ISendMessages MessageSender { get; set; } - public int SecondsToSleepBetweenPolls { get; set; } - public DefaultTimeoutManager TimeoutManager { get; set; } - public CriticalError CriticalError { get; set; } - public Address DispatcherAddress { get; set; } - public TimeSpan TimeToWaitBeforeTriggeringCriticalError { get; set; } - - public void Dispose() - { - //Injected - } - - public void Start() - { - circuitBreaker = new RepeatedFailuresOverTimeCircuitBreaker("TimeoutStorageConnectivity", TimeToWaitBeforeTriggeringCriticalError, - ex => - { - CriticalError.Raise("Repeated failures when fetching timeouts from storage, endpoint will be terminated.", ex); - }); - - TimeoutManager.TimeoutPushed = TimeoutsManagerOnTimeoutPushed; - - SecondsToSleepBetweenPolls = 1; - - tokenSource = new CancellationTokenSource(); - - StartPoller(); - } - - public void Stop() - { - TimeoutManager.TimeoutPushed = null; - tokenSource.Cancel(); - resetEvent.WaitOne(); - } - - void StartPoller() - { - var token = tokenSource.Token; - - Task.Factory - .StartNew(Poll, token, TaskCreationOptions.LongRunning) - .ContinueWith(t => - { - t.Exception.Handle(ex => - { - Logger.Warn("Failed to fetch timeouts from the timeout storage", ex); - circuitBreaker.Failure(ex); - return true; - }); - - StartPoller(); - }, TaskContinuationOptions.OnlyOnFaulted); - } - - void Poll(object obj) - { - var cancellationToken = (CancellationToken)obj; - - var startSlice = DateTime.UtcNow.AddYears(-10); - - resetEvent.Reset(); - - while (!cancellationToken.IsCancellationRequested) - { - if (nextRetrieval > DateTime.UtcNow) - { - Thread.Sleep(SecondsToSleepBetweenPolls * 1000); - continue; - } - - Logger.DebugFormat("Polling for timeouts at {0}.", DateTime.Now); - - DateTime nextExpiredTimeout; - var timeoutDatas = TimeoutsPersister.GetNextChunk(startSlice, out nextExpiredTimeout); - - foreach (var timeoutData in timeoutDatas) - { - if (startSlice < timeoutData.Item2) - { - startSlice = timeoutData.Item2; - } - - MessageSender.Send(CreateTransportMessage(timeoutData.Item1), new SendOptions(DispatcherAddress)); - } - - lock (lockObject) - { - //Check if nextRetrieval has been modified (This means that a push come in) and if it has check if it is earlier than nextExpiredTimeout time - if (!timeoutPushed) - { - nextRetrieval = nextExpiredTimeout; - } - else if (nextExpiredTimeout < nextRetrieval) - { - nextRetrieval = nextExpiredTimeout; - } - - timeoutPushed = false; - } - - // we cap the next retrieval to max 1 minute this will make sure that we trip the circuit breaker if we - // loose connectivity to our storage. This will also make sure that timeouts added (during migration) direct to storage - // will be picked up after at most 1 minute - var maxNextRetrieval = DateTime.UtcNow + TimeSpan.FromMinutes(1); - - if (nextRetrieval > maxNextRetrieval) - { - nextRetrieval = maxNextRetrieval; - } - - Logger.DebugFormat("Polling next retrieval is at {0}.", nextRetrieval.ToLocalTime()); - circuitBreaker.Success(); - } - - resetEvent.Set(); - } - - static TransportMessage CreateTransportMessage(string timeoutId) - { - //use the dispatcher as the reply to address so that retries go back to the dispatcher q - // instead of the main endpoint q - var transportMessage = ControlMessage.Create(); - - transportMessage.Headers["Timeout.Id"] = timeoutId; - - return transportMessage; - } - - void TimeoutsManagerOnTimeoutPushed(TimeoutData timeoutData) - { - lock (lockObject) - { - if (nextRetrieval > timeoutData.Time) - { - nextRetrieval = timeoutData.Time; - timeoutPushed = true; - } - } - } - - static ILog Logger = LogManager.GetLogger(); - - RepeatedFailuresOverTimeCircuitBreaker circuitBreaker; - - readonly object lockObject = new object(); - ManualResetEvent resetEvent = new ManualResetEvent(true); - DateTime nextRetrieval = DateTime.UtcNow; - volatile bool timeoutPushed; - CancellationTokenSource tokenSource; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/TimeoutManager.cs b/src/NServiceBus.Core/Timeout/TimeoutManager.cs deleted file mode 100644 index 32166186281..00000000000 --- a/src/NServiceBus.Core/Timeout/TimeoutManager.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using Config; - using Settings; - using Timeout.Core; - using Timeout.Hosting.Windows; - - /// - /// Used to configure the timeout manager that provides message deferral. - /// - public class TimeoutManager : Feature - { - const int MinutesToWaitBeforeErrorForTimeoutPersisterReceiver = 2; - - internal TimeoutManager() - { - Defaults(s => s.SetDefault("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver", TimeSpan.FromMinutes(MinutesToWaitBeforeErrorForTimeoutPersisterReceiver))); - - DependsOn(); - - Prerequisite(context => !context.Settings.GetOrDefault("Endpoint.SendOnly"),"Send only endpoints can't use the timeoutmanager since it requires receive capabilities"); - - Prerequisite(context => - { - var distributorEnabled = context.Settings.GetOrDefault("Distributor.Enabled"); - var workerEnabled = context.Settings.GetOrDefault("Worker.Enabled"); - - return distributorEnabled || !workerEnabled; - },"This endpoint is a worker and will be using the timeoutmanager running at its masternode instead"); - - Prerequisite(context => !HasAlternateTimeoutManagerBeenConfigured(context.Settings),"A user configured timeoutmanager address has been found and this endpoint will send timeouts to that endpoint"); - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - var localAddress = context.Settings.LocalAddress(); - var dispatcherAddress = localAddress.SubScope("TimeoutsDispatcher"); - var inputAddress = localAddress.SubScope("Timeouts"); - - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(t => t.Disabled, false) - .ConfigureProperty(t => t.InputAddress, inputAddress) - .ConfigureProperty(t => t.EndpointName, context.Settings.EndpointName()); - - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(t => t.Disabled, false) - .ConfigureProperty(t => t.InputAddress, dispatcherAddress); - - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(t => t.TimeToWaitBeforeTriggeringCriticalError, context.Settings.Get("TimeToWaitBeforeTriggeringCriticalErrorForTimeoutPersisterReceiver")) - .ConfigureProperty(t => t.DispatcherAddress, dispatcherAddress); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - - bool HasAlternateTimeoutManagerBeenConfigured(ReadOnlySettings settings) - { - var unicastConfig = settings.GetConfigSection(); - - return unicastConfig != null && !string.IsNullOrWhiteSpace(unicastConfig.TimeoutManagerAddress); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Timeout/TimeoutManagerBasedDeferral.cs b/src/NServiceBus.Core/Timeout/TimeoutManagerBasedDeferral.cs deleted file mode 100644 index cc8e8ceed65..00000000000 --- a/src/NServiceBus.Core/Timeout/TimeoutManagerBasedDeferral.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.Features -{ - using Config; - using Timeout; - - /// - /// Adds the ability to defer messages using a timeoutmanager - /// - public class TimeoutManagerBasedDeferral:Feature - { - internal TimeoutManagerBasedDeferral() - { - - } - /// - /// Invoked if the feature is activated - /// - /// The feature context - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.TimeoutManagerAddress, GetTimeoutManagerAddress(context)); - } - - static Address GetTimeoutManagerAddress(FeatureConfigurationContext context) - { - var unicastConfig = context.Settings.GetConfigSection(); - - if (unicastConfig != null && !string.IsNullOrWhiteSpace(unicastConfig.TimeoutManagerAddress)) - { - return Address.Parse(unicastConfig.TimeoutManagerAddress); - } - - return context.Settings.Get
("MasterNode.Address").SubScope("Timeouts"); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/TransactionalConfigManager_Obsolete.cs b/src/NServiceBus.Core/TransactionalConfigManager_Obsolete.cs deleted file mode 100644 index 43de4409071..00000000000 --- a/src/NServiceBus.Core/TransactionalConfigManager_Obsolete.cs +++ /dev/null @@ -1,50 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using System.Transactions; - - [ObsoleteEx( - Message = "Use `configuration.Transactions().Enable()` or `configuration.Transactions().Disable()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static class TransactionalConfigManager - { - [ObsoleteEx( - Message = "Use `configuration.Transactions()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static Configure IsTransactional(this Configure config, bool value) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.Transactions().Disable()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static Configure DontUseTransactions(this Configure config) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.Transactions().IsolationLevel(IsolationLevel.Chaos)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static Configure IsolationLevel(this Configure config, IsolationLevel isolationLevel) - { - throw new NotImplementedException(); - } - - [ObsoleteEx( - Message = "Use `configuration.Transactions().DefaultTimeout(TimeSpan.FromMinutes(5))`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - TreatAsErrorFromVersion = "5.0", - RemoveInVersion = "6.0")] - public static Configure TransactionTimeout(this Configure config, TimeSpan transactionTimeout) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/NServiceBus.Core/TransportMessage.cs b/src/NServiceBus.Core/TransportMessage.cs deleted file mode 100644 index 02957b25652..00000000000 --- a/src/NServiceBus.Core/TransportMessage.cs +++ /dev/null @@ -1,214 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - - /// - /// An envelope used by NServiceBus to package messages for transmission. - /// - /// - /// All messages sent and received by NServiceBus are wrapped in this class. - /// More than one message can be bundled in the envelope to be transmitted or - /// received by the bus. - /// - [Serializable] - public class TransportMessage - { - /// - /// Initializes the transport message with a CombGuid as identifier - /// - public TransportMessage() - { - id = CombGuid.Generate().ToString(); - Headers[NServiceBus.Headers.MessageId] = id; - CorrelationId = id; - MessageIntent = MessageIntentEnum.Send; - Headers[NServiceBus.Headers.NServiceBusVersion] = GitFlowVersion.MajorMinorPatch; - Headers[NServiceBus.Headers.TimeSent] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); - } - - - /// - /// Creates a new TransportMessage with the given id and headers - /// - public TransportMessage(string existingId, Dictionary existingHeaders) - { - if (existingHeaders == null) - { - existingHeaders = new Dictionary(); - } - - headers = existingHeaders; - id = existingId; - - //only update the "stable id" if there isn't one present already - if (!Headers.ContainsKey(NServiceBus.Headers.MessageId)) - { - Headers[NServiceBus.Headers.MessageId] = existingId; - } - } - - /// - /// Creates a new TransportMessage with the given id and headers and reply to address - /// - [ObsoleteEx( - TreatAsErrorFromVersion = "5.1", - RemoveInVersion = "6", - Message = "Use `headers[Headers.ReplyToAddress]=replyToAddress; var tm = new TransportMessage(id,headers)` instead.")] - public TransportMessage(string existingId, Dictionary existingHeaders, Address replyToAddress):this(existingId,existingHeaders) - { - Headers[NServiceBus.Headers.ReplyToAddress] = replyToAddress.ToString(); - } - - /// - /// Gets/sets the identifier of this message bundle. - /// - public string Id - { - get - { - string headerId; - if (Headers.TryGetValue(NServiceBus.Headers.MessageId, out headerId)) - { - return headerId; - } - return id; - } - } - - /// - /// Gets/sets the unique identifier of another message bundle - /// this message bundle is associated with. - /// - public string CorrelationId - { - get - { - string correlationId; - - if (Headers.TryGetValue(NServiceBus.Headers.CorrelationId, out correlationId)) - { - return correlationId; - } - - return null; - } - set { Headers[NServiceBus.Headers.CorrelationId] = value; } - } - - /// - /// Gets/sets the reply-to address of the message bundle - replaces 'ReturnAddress'. - /// - public Address ReplyToAddress - { - get - { - string replyToAddress; - - if (Headers.TryGetValue(NServiceBus.Headers.ReplyToAddress, out replyToAddress)) - { - return Address.Parse(replyToAddress); - } - - return null; - } - } - - /// - /// Gets/sets whether or not the message is supposed to - /// be guaranteed deliverable. - /// - public bool Recoverable { get; set; } - - /// - /// Indicates to the infrastructure the message intent (publish, or regular send). - /// - public MessageIntentEnum MessageIntent - { - get - { - var messageIntent = default(MessageIntentEnum); - - string messageIntentString; - if (Headers.TryGetValue(NServiceBus.Headers.MessageIntent, out messageIntentString)) - { - Enum.TryParse(messageIntentString, true, out messageIntent); - } - - return messageIntent; - } - set { Headers[NServiceBus.Headers.MessageIntent] = value.ToString(); } - } - - - /// - /// Gets/sets the maximum time limit in which the message bundle - /// must be received. - /// - public TimeSpan TimeToBeReceived - { - get { return timeToBeReceived; } - set { timeToBeReceived = value; } - } - - /// - /// Gets/sets other applicative out-of-band information. - /// - public Dictionary Headers - { - get { return headers; } - } - - - /// - /// Gets/sets a byte array to the body content of the message - /// - public byte[] Body - { - get { return body; } - set { UpdateBody(value); } - } - - /// - /// Use this method to change the stable ID of the given message. - /// - internal void ChangeMessageId(string newId) - { - id = newId; - CorrelationId = newId; - } - - /// - /// Use this method to update the body if this message - /// - void UpdateBody(byte[] updatedBody) - { - //preserve the original body if needed - if (body != null && originalBody == null) - { - originalBody = new byte[body.Length]; - Buffer.BlockCopy(body, 0, originalBody, 0, body.Length); - } - - body = updatedBody; - } - - /// - /// Makes sure that the body is reset to the exact state as it was when the message was created - /// - internal void RevertToOriginalBodyIfNeeded() - { - if (originalBody != null) - { - body = originalBody; - } - } - - readonly Dictionary headers = new Dictionary(); - - byte[] body; - string id; - byte[] originalBody; - TimeSpan timeToBeReceived = TimeSpan.MaxValue; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ConfigurePurging.cs b/src/NServiceBus.Core/Transports/ConfigurePurging.cs index a45051e494e..4cadc171d91 100644 --- a/src/NServiceBus.Core/Transports/ConfigurePurging.cs +++ b/src/NServiceBus.Core/Transports/ConfigurePurging.cs @@ -1,32 +1,35 @@ namespace NServiceBus { + using System; + /// - /// Configures purging + /// Configures purging. /// - public static partial class ConfigurePurging + public static class ConfigurePurging { /// /// Requests that the incoming queue be purged of all messages when the bus is started. /// All messages in this queue will be deleted if this is true. - /// Setting this to true may make sense for certain smart-client applications, + /// Setting this to true may make sense for certain smart-client applications, /// but rarely for server applications. /// - public static void PurgeOnStartup(this BusConfiguration config, bool value) + /// The instance to apply the settings to. + /// True to purge all message on startup; otherwise False. + public static void PurgeOnStartup(this EndpointConfiguration config, bool value) { + Guard.AgainstNull(nameof(config), config); config.Settings.Set("Transport.PurgeOnStartup", value); } /// /// Retrieves whether to purge the queues at startup or not. /// + [ObsoleteEx( + TreatAsErrorFromVersion = "6")] +// ReSharper disable once UnusedParameter.Global public static bool PurgeOnStartup(this Configure config) { - bool purgeOnStartup; - if (config.Settings.TryGet("Transport.PurgeOnStartup", out purgeOnStartup)) - { - return purgeOnStartup; - } - return false; + throw new NotImplementedException(); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ConfigurePurging_Obsolete.cs b/src/NServiceBus.Core/Transports/ConfigurePurging_Obsolete.cs deleted file mode 100644 index a3c37ca866f..00000000000 --- a/src/NServiceBus.Core/Transports/ConfigurePurging_Obsolete.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class ConfigurePurging - { - - /// - /// Requests that the incoming queue be purged of all messages when the bus is started. - /// All messages in this queue will be deleted if this is true. - /// Setting this to true may make sense for certain smart-client applications, - /// but rarely for server applications. - /// - [ObsoleteEx( - Message = "Use `configuration.PurgeOnStartup()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure PurgeOnStartup(this Configure config, bool value) - { - throw new NotImplementedException(); - } - - /// - /// True if the users wants the input queue to be purged when we starts up - /// - [ObsoleteEx( - Message = "The `ReadOnlySettings` extension method `ConfigurePurging.GetPurgeOnStartup`", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static bool PurgeRequested - { - get - { - throw new NotImplementedException(); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ConfigureTransport.cs b/src/NServiceBus.Core/Transports/ConfigureTransport.cs deleted file mode 100644 index 132b5000fb0..00000000000 --- a/src/NServiceBus.Core/Transports/ConfigureTransport.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace NServiceBus.Transports -{ - using System; - using Features; - using NServiceBus.Settings; - using Unicast.Transport; - - /// - /// Base class for configuring features. - /// - public abstract class ConfigureTransport : Feature - { - /// - /// Initializes a new instance of . - /// - protected ConfigureTransport() - { - Defaults(s => s.SetDefault(TransportConnectionString.Default)); - - Defaults(s => s.SetDefault("NServiceBus.LocalAddress", GetDefaultEndpointAddress(s))); - - Defaults(s => - { - var localAddress = GetLocalAddress(s); - if (!String.IsNullOrEmpty(localAddress) && !s.HasExplicitValue("NServiceBus.LocalAddress")) - { - s.Set("NServiceBus.LocalAddress", localAddress); - } - }); - } - - /// - /// - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - var connectionString = context.Settings.Get().GetConnectionStringOrNull(); - var selectedTransportDefinition = context.Settings.Get(); - - if (connectionString == null && RequiresConnectionString) - { - throw new InvalidOperationException(String.Format(Message, GetConfigFileIfExists(), selectedTransportDefinition.GetType().Name, ExampleConnectionStringForErrorMessage)); - } - - context.Container.RegisterSingleton(selectedTransportDefinition); - - Configure(context, connectionString); - } - - /// - /// Allows the transport to control the local address of the endpoint if needed - /// - /// The current settings in read only mode - // ReSharper disable once UnusedParameter.Global - protected virtual string GetLocalAddress(ReadOnlySettings settings) - { - return null; - } - - /// - /// Gives the chance to implementers to set themselves up. - /// - protected abstract void Configure(FeatureConfigurationContext context, string connectionString); - - /// - /// Used by implementations to provide an example connection string that till be used for the possible exception thrown if the requirement is not met. - /// - protected abstract string ExampleConnectionStringForErrorMessage { get; } - - /// - /// Used by implementations to control if a connection string is necessary. - /// - /// If this is true and a connection string is not returned by then an exception will be thrown. - protected virtual bool RequiresConnectionString - { - get { return true; } - } - - static string GetConfigFileIfExists() - { - return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile ?? "App.config"; - } - - static string GetDefaultEndpointAddress(ReadOnlySettings settings) - { - if (!settings.GetOrDefault("IndividualizeEndpointAddress")) - { - return settings.EndpointName(); - } - - if (!settings.HasSetting("EndpointInstanceDiscriminator")) - { - throw new Exception("No endpoint instance discriminator found. This value is usually provided by your transport so please make sure you're on the lastest version of your specific transport or set the discriminator using 'configuration.ScaleOut().UniqueQueuePerEndpointInstance(myDiscriminator)'"); - } - - return settings.EndpointName() + settings.Get("EndpointInstanceDiscriminator"); - } - - - const string Message = - @"No default connection string found in your config file ({0}) for the {1} Transport. - -To run NServiceBus with {1} Transport you need to specify the database connectionstring. -Here is an example of what is required: - - - - "; - - } -} diff --git a/src/NServiceBus.Core/Transports/DispatchConsistency.cs b/src/NServiceBus.Core/Transports/DispatchConsistency.cs new file mode 100644 index 00000000000..3df10640bec --- /dev/null +++ b/src/NServiceBus.Core/Transports/DispatchConsistency.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Transport +{ + /// + /// The requested level of dispatch consistency. + /// + public enum DispatchConsistency + { + /// + /// The transport should use it's default mode when deciding to enlist the dispatch operation in the receive transaction. + /// + Default = 1, + + /// + /// The message should be dispatched immediately without enlisting in any ongoing receive transaction. + /// + Isolated = 2 + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ErrorContext.cs b/src/NServiceBus.Core/Transports/ErrorContext.cs new file mode 100644 index 00000000000..1267ccc29a1 --- /dev/null +++ b/src/NServiceBus.Core/Transports/ErrorContext.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Collections.Generic; + + /// + /// The context for messages that has failed processing. + /// + public class ErrorContext + { + /// + /// Initializes the error context. + /// + public ErrorContext(Exception exception, Dictionary headers, string transportMessageId, byte[] body, TransportTransaction transportTransaction, int immediateProcessingFailures) + { + Exception = exception; + TransportTransaction = transportTransaction; + ImmediateProcessingFailures = immediateProcessingFailures; + + Message = new IncomingMessage(transportMessageId, headers, body); + + DelayedDeliveriesPerformed = Message.GetDelayedDeliveriesPerformed(); + } + + /// + /// Exception that caused the message processing to fail. + /// + public Exception Exception { get; } + + /// + /// Transport transaction for failed receive message. + /// + public TransportTransaction TransportTransaction { get; } + + /// + /// Number of failed immediate processing attempts. This number is re-set with each delayed delivery. + /// + public int ImmediateProcessingFailures { get; } + + /// + /// Number of delayed deliveries performed so fat. + /// + public int DelayedDeliveriesPerformed { get; } + + /// + /// Failed incoming message. + /// + public IncomingMessage Message { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ErrorHandleResult.cs b/src/NServiceBus.Core/Transports/ErrorHandleResult.cs new file mode 100644 index 00000000000..3188d728dfe --- /dev/null +++ b/src/NServiceBus.Core/Transports/ErrorHandleResult.cs @@ -0,0 +1,18 @@ +namespace NServiceBus.Transport +{ + /// + /// Provides information about error handling. + /// + public enum ErrorHandleResult + { + /// + /// Indicates that the infrastructure handled the current error. + /// + Handled, + + /// + /// Indicates that the infrastructure was did not handle the current error. A retry is required. + /// + RetryRequired + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ICancelDeferredMessages.cs b/src/NServiceBus.Core/Transports/ICancelDeferredMessages.cs new file mode 100644 index 00000000000..47bfffd7dd6 --- /dev/null +++ b/src/NServiceBus.Core/Transports/ICancelDeferredMessages.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Transport +{ + using System.Threading.Tasks; + using Pipeline; + + /// + /// Allows timeouts to be canceled by the key provided when set. + /// + public interface ICancelDeferredMessages + { + /// + /// Clears all timeouts for the given timeout key. + /// + Task CancelDeferredMessages(string messageKey, IBehaviorContext context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ICreateQueues.cs b/src/NServiceBus.Core/Transports/ICreateQueues.cs index eea24259faa..af35a7e9e9b 100644 --- a/src/NServiceBus.Core/Transports/ICreateQueues.cs +++ b/src/NServiceBus.Core/Transports/ICreateQueues.cs @@ -1,13 +1,15 @@ -namespace NServiceBus.Transports +namespace NServiceBus.Transport { + using System.Threading.Tasks; + /// - /// Abstraction of the capability to create queues + /// Abstraction of the capability to create queues. /// public interface ICreateQueues { /// - /// Create a messages queue where its name is the address parameter, for the given account. + /// Creates message queues for the defined queue bindings and identity. /// - void CreateQueueIfNecessary(Address address, string account); + Task CreateQueueIfNecessary(QueueBindings queueBindings, string identity); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IDeferMessages.cs b/src/NServiceBus.Core/Transports/IDeferMessages.cs deleted file mode 100644 index 6b47ec12b1d..00000000000 --- a/src/NServiceBus.Core/Transports/IDeferMessages.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Transports -{ - using Unicast; - - /// - /// Called when the bus wants to defer a message - /// - public interface IDeferMessages - { - /// - /// Defers the given message - /// - void Defer(TransportMessage message, SendOptions sendOptions); - - /// - /// Clears all timeouts for the given header - /// - void ClearDeferredMessages(string headerKey, string headerValue); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IDequeueMessages.cs b/src/NServiceBus.Core/Transports/IDequeueMessages.cs deleted file mode 100644 index e3fd1a5e8b9..00000000000 --- a/src/NServiceBus.Core/Transports/IDequeueMessages.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus.Transports -{ - using System; - using Unicast.Transport; - - /// - /// Interface to implement when developing custom dequeuing strategies. - /// - public interface IDequeueMessages - { - /// - /// Initializes the . - /// - /// The address to listen on. - /// The to be used by . - /// Called when a message has been dequeued and is ready for processing. - /// Needs to be called by after the message has been processed regardless if the outcome was successful or not. - void Init(Address address, TransactionSettings transactionSettings, Func tryProcessMessage, Action endProcessMessage); - - /// - /// Starts the dequeuing of message using the specified . - /// - /// Indicates the maximum concurrency level this is able to support. - void Start(int maximumConcurrencyLevel); - - /// - /// Stops the dequeuing of messages. - /// - void Stop(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IDispatchMessages.cs b/src/NServiceBus.Core/Transports/IDispatchMessages.cs new file mode 100644 index 00000000000..290fdd0ac31 --- /dev/null +++ b/src/NServiceBus.Core/Transports/IDispatchMessages.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Transport +{ + using System.Threading.Tasks; + using Extensibility; + + /// + /// Abstraction of the capability to dispatch messages. + /// + public interface IDispatchMessages + { + /// + /// Dispatches the given operations to the transport. + /// + Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IManageSubscriptions.cs b/src/NServiceBus.Core/Transports/IManageSubscriptions.cs index d432f042dd1..d5312b0cc40 100644 --- a/src/NServiceBus.Core/Transports/IManageSubscriptions.cs +++ b/src/NServiceBus.Core/Transports/IManageSubscriptions.cs @@ -1,24 +1,26 @@ -namespace NServiceBus.Transports +namespace NServiceBus.Transport { using System; + using System.Threading.Tasks; + using Extensibility; /// - /// Implemented by transports to provide pub/sub capabilities + /// Implemented by transports to provide pub/sub capabilities. /// public interface IManageSubscriptions { /// - /// Subscribes to the given event. For message driven transports like msmq and sqlserver the address of the publisher is needed as well + /// Subscribes to the given event. /// - /// The event type - /// The publisher address if needed - void Subscribe(Type eventType, Address publisherAddress); - + /// The event type. + /// The current context. + Task Subscribe(Type eventType, ContextBag context); + /// - /// Unsubscribes from the given event. For message driven transports like msmq and sqlserver the address of the publisher is needed as well + /// Unsubscribes from the given event. /// - /// The event type - /// The publisher address if needed - void Unsubscribe(Type eventType, Address publisherAddress); + /// The event type. + /// The current context. + Task Unsubscribe(Type eventType, ContextBag context); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IOutgoingTransportOperation.cs b/src/NServiceBus.Core/Transports/IOutgoingTransportOperation.cs new file mode 100644 index 00000000000..60f24c219c4 --- /dev/null +++ b/src/NServiceBus.Core/Transports/IOutgoingTransportOperation.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + using DeliveryConstraints; + + /// + /// Represents a transport operation. + /// + public interface IOutgoingTransportOperation + { + /// + /// The message to be sent over the transport. + /// + OutgoingMessage Message { get; } + + /// + /// The delivery constraints that must be honored by the transport. + /// + List DeliveryConstraints { get; } + + /// + /// The dispatch consistency the must be honored by the transport. + /// + DispatchConsistency RequiredDispatchConsistency { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IPublishMessages.cs b/src/NServiceBus.Core/Transports/IPublishMessages.cs deleted file mode 100644 index f9fed3a7c7d..00000000000 --- a/src/NServiceBus.Core/Transports/IPublishMessages.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.Transports -{ - using Unicast; - - /// - /// Requests a message to be published - /// - public interface IPublishMessages - { - /// - /// Publishes the given messages to all known subscribers - /// - void Publish(TransportMessage message,PublishOptions publishOptions); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IPushMessages.cs b/src/NServiceBus.Core/Transports/IPushMessages.cs new file mode 100644 index 00000000000..e86f72d1c00 --- /dev/null +++ b/src/NServiceBus.Core/Transports/IPushMessages.cs @@ -0,0 +1,33 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Threading.Tasks; + + /// + /// Allows the transport to push messages to the core. + /// + public interface IPushMessages + { + /// + /// Prepare the message pump to be started. + /// + /// Called when there is a message available for processing. + /// Called when there is a message has failed mprocessing. + /// Called when there is a critical error in the message pump. + /// Runtime settings for the message pump. + Task Init(Func onMessage, + Func> onError, + CriticalError criticalError, + PushSettings settings); + + /// + /// Starts pushing messages. + /// + void Start(PushRuntimeSettings limitations); + + /// + /// Stops pushing messages. + /// + Task Stop(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ISendMessages.cs b/src/NServiceBus.Core/Transports/ISendMessages.cs deleted file mode 100644 index cb7443b074d..00000000000 --- a/src/NServiceBus.Core/Transports/ISendMessages.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.Transports -{ - using Unicast; - - /// - /// Abstraction of the capability to send messages. - /// - public interface ISendMessages - { - /// - /// Sends the given - /// - void Send(TransportMessage message, SendOptions sendOptions); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IncomingMessage.cs b/src/NServiceBus.Core/Transports/IncomingMessage.cs new file mode 100644 index 00000000000..e61ed8a4ae5 --- /dev/null +++ b/src/NServiceBus.Core/Transports/IncomingMessage.cs @@ -0,0 +1,85 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Collections.Generic; + + /// + /// The raw message coming from the transport. + /// + public class IncomingMessage + { + /// + /// Creates a new message. + /// + /// Native message id. + /// The message headers. + /// The message body. + public IncomingMessage(string messageId, Dictionary headers, byte[] body) + { + Guard.AgainstNullAndEmpty(nameof(messageId), messageId); + Guard.AgainstNull(nameof(body), body); + Guard.AgainstNull(nameof(headers), headers); + + string originalMessageId; + + if (headers.TryGetValue(NServiceBus.Headers.MessageId, out originalMessageId) && !string.IsNullOrEmpty(originalMessageId)) + { + MessageId = originalMessageId; + } + else + { + MessageId = messageId; + + headers[NServiceBus.Headers.MessageId] = messageId; + } + + + Headers = headers; + + Body = body; + } + + /// + /// The id of the message. + /// + public string MessageId { get; private set; } + + /// + /// The message headers. + /// + public Dictionary Headers { get; private set; } + + /// + /// Gets/sets a byte array to the body content of the message. + /// + public byte[] Body { get; private set; } + + /// + /// Use this method to update the body if this message. + /// + internal void UpdateBody(byte[] updatedBody) + { + //preserve the original body if needed + if (Body != null && originalBody == null) + { + originalBody = new byte[Body.Length]; + Buffer.BlockCopy(Body, 0, originalBody, 0, Body.Length); + } + + Body = updatedBody; + } + + /// + /// Makes sure that the body is reset to the exact state as it was when the message was created. + /// + internal void RevertToOriginalBodyIfNeeded() + { + if (originalBody != null) + { + Body = originalBody; + } + } + + byte[] originalBody; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/IncomingMessageExtensions.cs b/src/NServiceBus.Core/Transports/IncomingMessageExtensions.cs new file mode 100644 index 00000000000..9863bb03a36 --- /dev/null +++ b/src/NServiceBus.Core/Transports/IncomingMessageExtensions.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.Transport +{ + using System; + + /// + /// Helper methods for . + /// + public static class IncomingMessageExtensions + { + /// + /// Gets the message intent from the headers. + /// + /// The message. + /// The message intent. + public static MessageIntentEnum GetMesssageIntent(this IncomingMessage message) + { + var messageIntent = default(MessageIntentEnum); + + string messageIntentString; + if (message.Headers.TryGetValue(Headers.MessageIntent, out messageIntentString)) + { + Enum.TryParse(messageIntentString, true, out messageIntent); + } + + return messageIntent; + } + + /// + /// Gets the reply to address. + /// + /// The message. + /// The reply to address. + public static string GetReplyToAddress(this IncomingMessage message) + { + string replyToAddress; + + return message.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress) ? replyToAddress : null; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/LogicalAddressExtensions.cs b/src/NServiceBus.Core/Transports/LogicalAddressExtensions.cs new file mode 100644 index 00000000000..2f9e0ff8bf3 --- /dev/null +++ b/src/NServiceBus.Core/Transports/LogicalAddressExtensions.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Transport +{ + using Settings; + + /// + /// Extension methods for access to various transport address helpers. + /// + public static class LogicalAddressExtensions + { + /// + /// Gets the native transport address for the given logical address. + /// + /// The native transport address. + public static string GetTransportAddress(this ReadOnlySettings settings, LogicalAddress logicalAddress) + { + return settings.Get().ToTransportAddress(logicalAddress); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/MessageContext.cs b/src/NServiceBus.Core/Transports/MessageContext.cs new file mode 100644 index 00000000000..1e1e1ac94e3 --- /dev/null +++ b/src/NServiceBus.Core/Transports/MessageContext.cs @@ -0,0 +1,73 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + using System.Threading; + using Extensibility; + + /// + /// Allows the transport to pass relevant info to the pipeline. + /// + public class MessageContext + { + /// + /// Initializes the context. + /// + /// Native message id. + /// The message headers. + /// The message body. + /// Transaction (along with connection if applicable) used to receive the message. + /// + /// Allows the pipeline to flag that it has been aborted and the receive operation should be rolled back. + /// It also allows the transport to communicate to the pipeline to abort if possible. Transports should check if the token + /// has been aborted after invoking the pipeline and roll back the message accordingly. + /// + /// Any context that the transport wants to be available on the pipeline. + public MessageContext(string messageId, Dictionary headers, byte[] body, TransportTransaction transportTransaction, CancellationTokenSource receiveCancellationTokenSource, ContextBag context) + { + Guard.AgainstNullAndEmpty(nameof(messageId), messageId); + Guard.AgainstNull(nameof(body), body); + Guard.AgainstNull(nameof(headers), headers); + Guard.AgainstNull(nameof(transportTransaction), transportTransaction); + Guard.AgainstNull(nameof(receiveCancellationTokenSource), receiveCancellationTokenSource); + Guard.AgainstNull(nameof(context), context); + + Headers = headers; + Body = body; + MessageId = messageId; + Context = context; + TransportTransaction = transportTransaction; + ReceiveCancellationTokenSource = receiveCancellationTokenSource; + } + + /// + /// The native id of the message. + /// + public string MessageId { get; } + + /// + /// The message headers. + /// + public Dictionary Headers { get; } + + /// + /// The message body. + /// + public byte[] Body { get; } + + /// + /// Transaction (along with connection if applicable) used to receive the message. + /// + public TransportTransaction TransportTransaction { get; } + + /// + /// Allows the pipeline to flag that the pipeline has been aborted and the receive operation should be rolled back. + /// It also allows the transport to communicate to the pipeline to abort if possible. + /// + public CancellationTokenSource ReceiveCancellationTokenSource { get; } + + /// + /// Context provided by the transport. + /// + public ContextBag Context { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/Config/CheckMachineNameForComplianceWithDtcLimitation.cs b/src/NServiceBus.Core/Transports/Msmq/CheckMachineNameForComplianceWithDtcLimitation.cs similarity index 80% rename from src/NServiceBus.Core/Transports/Msmq/Config/CheckMachineNameForComplianceWithDtcLimitation.cs rename to src/NServiceBus.Core/Transports/Msmq/CheckMachineNameForComplianceWithDtcLimitation.cs index 9883dda06c7..b69b64ec97b 100644 --- a/src/NServiceBus.Core/Transports/Msmq/Config/CheckMachineNameForComplianceWithDtcLimitation.cs +++ b/src/NServiceBus.Core/Transports/Msmq/CheckMachineNameForComplianceWithDtcLimitation.cs @@ -9,35 +9,38 @@ class CheckMachineNameForComplianceWithDtcLimitation [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern bool GetComputerNameEx(COMPUTER_NAME_FORMAT nameType, [Out] StringBuilder lpBuffer, ref uint lpnSize); - enum COMPUTER_NAME_FORMAT - { - ComputerNameNetBIOS, - ComputerNameDnsHostname, - ComputerNameDnsDomain, - ComputerNameDnsFullyQualified, - ComputerNamePhysicalNetBIOS, - ComputerNamePhysicalDnsHostname, - ComputerNamePhysicalDnsDomain, - ComputerNamePhysicalDnsFullyQualified - } - - static ILog Logger = LogManager.GetLogger(); - /// /// Method invoked to run custom code. /// public void Check() { - uint capacity = 24; - var buffer = new StringBuilder((int)capacity); + var buffer = new StringBuilder((int) capacity); if (!GetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNameNetBIOS, buffer, ref capacity)) + { return; + } var netbiosName = buffer.ToString(); - if (netbiosName.Length <= 15) return; + if (netbiosName.Length <= 15) + { + return; + } + + Logger.WarnFormat("NetBIOS name [{0}] is longer than 15 characters. Shorten it for DTC to work.", netbiosName); + } + + static ILog Logger = LogManager.GetLogger(); - Logger.Warn(string.Format( - "NetBIOS name [{0}] is longer than 15 characters. Shorten it for DTC to work.", netbiosName)); + enum COMPUTER_NAME_FORMAT + { + ComputerNameNetBIOS, + ComputerNameDnsHostname, + ComputerNameDnsDomain, + ComputerNameDnsFullyQualified, + ComputerNamePhysicalNetBIOS, + ComputerNamePhysicalDnsHostname, + ComputerNamePhysicalDnsDomain, + ComputerNamePhysicalDnsFullyQualified } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/Config/ConfigureMsmqMessageQueue_Obsolete.cs b/src/NServiceBus.Core/Transports/Msmq/Config/ConfigureMsmqMessageQueue_Obsolete.cs deleted file mode 100644 index 5b42459efdc..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/Config/ConfigureMsmqMessageQueue_Obsolete.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - [ObsoleteEx(Message = "Please use 'UsingTransport' on your 'IConfigureThisEndpoint' class or use `configuration.UseTransport()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0")] - public static class ConfigureMsmqMessageQueue - { - [ObsoleteEx(Message = "Please use 'UsingTransport' on your 'IConfigureThisEndpoint' class or use `configuration.UseTransport()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0")] - public static Configure MsmqTransport(this Configure config) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/Config/Msmq.cs b/src/NServiceBus.Core/Transports/Msmq/Config/Msmq.cs deleted file mode 100644 index 7c616a8edd3..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/Config/Msmq.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace NServiceBus -{ - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Features; - using Transports; - - /// - /// Transport definition for MSMQ - /// - public class MsmqTransport : TransportDefinition - { - /// - /// Ctor - /// - public MsmqTransport() - { - RequireOutboxConsent = true; - } - - /// - /// Gives implementations access to the instance at configuration time. - /// - protected internal override void Configure(BusConfiguration config) - { - // For MSMQ the endpoint differentiator is a no-op since you commonly scale out by running the same endpoint on a different machine. - // if users want to run more than one instance on the same machine they need to set an explicit discriminator - config.GetSettings() - .SetDefault("EndpointInstanceDiscriminator", ""); - - config.EnableFeature(); - config.EnableFeature(); - config.EnableFeature(); - - config.Settings.EnableFeatureByDefault(); - config.Settings.EnableFeatureByDefault(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/Config/MsmqSettings.cs b/src/NServiceBus.Core/Transports/Msmq/Config/MsmqSettings.cs deleted file mode 100644 index df6896faaad..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/Config/MsmqSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus.Transports.Msmq.Config -{ - using System; - using System.Messaging; - - /// - /// Runtime settings for the Msmq transport - /// - public class MsmqSettings - { - - /// - /// Constructs the settings class with defaults - /// - public MsmqSettings() - { - UseDeadLetterQueue = true; - UseConnectionCache = true; - UseTransactionalQueues = true; - TimeToReachQueue = Message.InfiniteTimeout; - } - - /// - /// Determines if the dead letter queue should be used - /// - public bool UseDeadLetterQueue { get; set; } - - /// - /// Determines if journaling should be activated - /// - public bool UseJournalQueue { get; set; } - - /// - /// Gets or sets a value that indicates whether a cache of connections will be maintained by the application. - /// - public bool UseConnectionCache { get; set; } - - /// - /// Determines if the system uses transactional queues - /// - public bool UseTransactionalQueues { get; set; } - - /// - /// Gets or sets the maximum amount of time for the message to reach the queue. - /// - public TimeSpan TimeToReachQueue { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/Config/MsmqTransportConfigurator.cs b/src/NServiceBus.Core/Transports/Msmq/Config/MsmqTransportConfigurator.cs deleted file mode 100644 index a427f1e8325..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/Config/MsmqTransportConfigurator.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace NServiceBus.Features -{ - using Config; - using Logging; - using NServiceBus.Faults; - using Transports; - using Transports.Msmq; - using Transports.Msmq.Config; - - /// - /// Used to configure the MSMQ transport. - /// - public class MsmqTransportConfigurator : ConfigureTransport - { - internal MsmqTransportConfigurator() - { - - } - - /// - /// Initializes a new instance of . - /// - protected override void Configure(FeatureConfigurationContext context, string connectionString) - { - new CheckMachineNameForComplianceWithDtcLimitation() - .Check(); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - - if (!context.Settings.GetOrDefault("Endpoint.SendOnly")) - { - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.ErrorQueue, ErrorQueueSettings.GetConfiguredErrorQueue(context.Settings)); - } - - var cfg = context.Settings.GetConfigSection(); - - var settings = new MsmqSettings(); - if (cfg != null) - { - settings.UseJournalQueue = cfg.UseJournalQueue; - settings.UseDeadLetterQueue = cfg.UseDeadLetterQueue; - - Logger.Warn(Message); - } - else - { - if (connectionString != null) - { - settings = new MsmqConnectionStringBuilder(connectionString).RetrieveSettings(); - } - } - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.Settings, settings) - .ConfigureProperty(t => t.SuppressDistributedTransactions, context.Settings.Get("Transactions.SuppressDistributedTransactions")); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(t => t.Settings, settings); - } - - /// - /// - /// - protected override string ExampleConnectionStringForErrorMessage - { - get { return "cacheSendConnection=true;journal=false;deadLetter=true"; } - } - - /// - /// - /// - protected override bool RequiresConnectionString - { - get { return false; } - } - - static ILog Logger = LogManager.GetLogger(); - - const string Message = - @" -MsmqMessageQueueConfig section has been deprecated in favor of using a connectionString instead. -Here is an example of what is required: - - - "; - - - - } - -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/CorrelationIdMutatorForBackwardsCompatibilityWithV3.cs b/src/NServiceBus.Core/Transports/Msmq/CorrelationIdMutatorForBackwardsCompatibilityWithV3.cs deleted file mode 100644 index ccea92ddc78..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/CorrelationIdMutatorForBackwardsCompatibilityWithV3.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - using MessageMutator; - using Unicast.Messages; - - [ObsoleteEx(RemoveInVersion ="6.0")] - class CorrelationIdMutatorForBackwardsCompatibilityWithV3 : IMutateOutgoingTransportMessages - { - public void MutateOutgoing(LogicalMessage logicalMessage, TransportMessage transportMessage) - { - if (transportMessage.Headers.ContainsKey(CorrIdHeader)) - return; - - var correlationIdToUse = transportMessage.CorrelationId; - Guid correlationId; - - if (Guid.TryParse(correlationIdToUse, out correlationId)) - { - //msmq requires the id's to be in the {guid}\{incrementing number} format so we need to fake a \0 at the end to make it compatible - correlationIdToUse += "\\0"; - } - transportMessage.Headers[CorrIdHeader] = correlationIdToUse; - } - - static string CorrIdHeader = "CorrId"; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/EndpointInstanceExtensions.cs b/src/NServiceBus.Core/Transports/Msmq/EndpointInstanceExtensions.cs new file mode 100644 index 00000000000..76449a01998 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/EndpointInstanceExtensions.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using Routing; + + /// + /// Provides MSMQ-specific extensions to routing. + /// + public static class EndpointInstanceExtensions + { + /// + /// Returns an endpoint instance bound to a given machine name. + /// + /// A plain instance. + /// Machine name. + public static EndpointInstance AtMachine(this EndpointInstance instance, string machineName) + { + Guard.AgainstNull(nameof(instance), instance); + Guard.AgainstNullAndEmpty(nameof(machineName), machineName); + return instance.SetProperty("machine", machineName); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/HeaderInfo.cs b/src/NServiceBus.Core/Transports/Msmq/HeaderInfo.cs index dab4dafce67..9b51f22f09b 100644 --- a/src/NServiceBus.Core/Transports/Msmq/HeaderInfo.cs +++ b/src/NServiceBus.Core/Transports/Msmq/HeaderInfo.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Transports.Msmq +namespace NServiceBus.Transport.Msmq { using System; @@ -18,4 +18,4 @@ public class HeaderInfo ///
public string Value { get; set; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/IInstanceMappingFileAccess.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/IInstanceMappingFileAccess.cs new file mode 100644 index 00000000000..00ccf4abc2c --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/IInstanceMappingFileAccess.cs @@ -0,0 +1,9 @@ +namespace NServiceBus +{ + using System.Xml.Linq; + + interface IInstanceMappingFileAccess + { + XDocument Load(string path); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileAccess.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileAccess.cs new file mode 100644 index 00000000000..7cc15c4eed7 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileAccess.cs @@ -0,0 +1,22 @@ +namespace NServiceBus +{ + using System.IO; + using System.Xml; + using System.Xml.Linq; + + class InstanceMappingFileAccess : IInstanceMappingFileAccess + { + public XDocument Load(string path) + { + XDocument doc; + using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var reader = XmlReader.Create(file)) + { + doc = XDocument.Load(reader); + } + } + return doc; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileFeature.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileFeature.cs new file mode 100644 index 00000000000..aa09a0fe05c --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileFeature.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.Features +{ + using System; + using System.IO; + using Routing; + + class InstanceMappingFileFeature : Feature + { + public InstanceMappingFileFeature() + { + Defaults(s => + { + s.SetDefault(CheckIntervalSettingsKey, TimeSpan.FromSeconds(30)); + s.SetDefault(FilePathSettingsKey, DefaultInstanceMappingFileName); + }); + Prerequisite(c => c.Settings.HasExplicitValue(FilePathSettingsKey) || File.Exists(GetRootedPath(DefaultInstanceMappingFileName)), "No explicit instance mapping file configuration and default file does not exist."); + DependsOn(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var filePath = GetRootedPath(context.Settings.Get(FilePathSettingsKey)); + + if (!File.Exists(filePath)) + { + throw new Exception($"The specified instance mapping file '{filePath}' does not exist."); + } + + var checkInterval = context.Settings.Get(CheckIntervalSettingsKey); + var endpointInstances = context.Settings.Get(); + + var instanceMappingTable = new InstanceMappingFileMonitor(filePath, checkInterval, new AsyncTimer(), new InstanceMappingFileAccess(), endpointInstances); + instanceMappingTable.ReloadData(); + context.RegisterStartupTask(instanceMappingTable); + } + + static string GetRootedPath(string filePath) + { + return Path.IsPathRooted(filePath) + ? filePath + : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath); + } + + public const string CheckIntervalSettingsKey = "InstanceMappingFile.CheckInterval"; + public const string FilePathSettingsKey = "InstanceMappingFile.FilePath"; + const string DefaultInstanceMappingFileName = "instance-mapping.xml"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileMonitor.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileMonitor.cs new file mode 100644 index 00000000000..21b3a46768c --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileMonitor.cs @@ -0,0 +1,109 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Features; + using Logging; + using Routing; + + class InstanceMappingFileMonitor : FeatureStartupTask + { + public InstanceMappingFileMonitor(string filePath, TimeSpan checkInterval, IAsyncTimer timer, IInstanceMappingFileAccess fileAccess, EndpointInstances endpointInstances) + { + this.filePath = filePath; + this.checkInterval = checkInterval; + this.timer = timer; + this.fileAccess = fileAccess; + this.endpointInstances = endpointInstances; + } + + protected override Task OnStart(IMessageSession session) + { + timer.Start(() => + { + ReloadData(); + return TaskEx.CompletedTask; + }, checkInterval, ex => log.Error("Unable to update instance mapping information because the instance mapping file couldn't be read.", ex)); + return TaskEx.CompletedTask; + } + + public void ReloadData() + { + try + { + var doc = fileAccess.Load(filePath); + var instances = parser.Parse(doc); + LogChanges(instances, filePath); + endpointInstances.AddOrReplaceInstances("InstanceMappingFile", instances); + } + catch (Exception ex) + { + throw new Exception($"An error occurred while reading the endpoint instance mapping file at {filePath}. See the inner exception for more details.", ex); + } + } + + void LogChanges(List instances, string filepath) + { + var output = new StringBuilder(); + var hasChanges = false; + + var instancesPerEndpoint = instances.GroupBy(i => i.Endpoint).ToDictionary(g => g.Key, g => g.ToArray()); + + output.AppendLine($"Updating instance mapping table from '{filepath}':"); + + foreach (var endpoint in instancesPerEndpoint) + { + EndpointInstance[] existingInstances; + if (previousInstances.TryGetValue(endpoint.Key, out existingInstances)) + { + var newInstances = endpoint.Value.Except(existingInstances).Count(); + var removedInstances = existingInstances.Except(endpoint.Value).Count(); + + if (newInstances > 0 || removedInstances > 0) + { + output.AppendLine($"Updated endpoint '{endpoint.Key}': +{Instances(newInstances)}, -{Instances(removedInstances)}"); + hasChanges = true; + } + } + else + { + output.AppendLine($"Added endpoint '{endpoint.Key}' with {Instances(endpoint.Value.Length)}"); + hasChanges = true; + } + } + + foreach (var removedEndpoint in previousInstances.Keys.Except(instancesPerEndpoint.Keys)) + { + output.AppendLine($"Removed all instances of endpoint '{removedEndpoint}'"); + hasChanges = true; + } + + if (hasChanges) + { + log.Info(output.ToString()); + } + + previousInstances = instancesPerEndpoint; + } + + static string Instances(int count) + { + return count > 1 ? $"{count} instances" : $"{count} instance"; + } + + protected override Task OnStop(IMessageSession session) => timer.Stop(); + + TimeSpan checkInterval; + IInstanceMappingFileAccess fileAccess; + string filePath; + EndpointInstances endpointInstances; + InstanceMappingFileParser parser = new InstanceMappingFileParser(); + IAsyncTimer timer; + IDictionary previousInstances = new Dictionary(0); + + static ILog log = LogManager.GetLogger(typeof(InstanceMappingFileMonitor)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileParser.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileParser.cs new file mode 100644 index 00000000000..3b367519772 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileParser.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using System.Xml.Linq; + using System.Xml.Schema; + using Routing; + + class InstanceMappingFileParser + { + public InstanceMappingFileParser() + { + using (var stream = GetType().Assembly.GetManifestResourceStream("NServiceBus.Transports.Msmq.InstanceMapping.endpoints.xsd")) + using (var xmlReader = XmlReader.Create(stream)) + { + schema = new XmlSchemaSet(); + schema.Add("", xmlReader); + } + } + + public List Parse(XDocument document) + { + document.Validate(schema, null, true); + + var root = document.Root; + var endpointElements = root.Descendants("endpoint"); + + var instances = new List(); + + foreach (var e in endpointElements) + { + var endpointName = e.Attribute("name").Value; + + foreach (var i in e.Descendants("instance")) + { + var discriminatorAttribute = i.Attribute("discriminator"); + var discriminator = discriminatorAttribute?.Value; + + var properties = i.Attributes().Where(a => a.Name != "discriminator"); + var propertyDictionary = properties.ToDictionary(a => a.Name.LocalName, a => a.Value); + + instances.Add(new EndpointInstance(endpointName, discriminator, propertyDictionary)); + } + } + + return instances; + } + + XmlSchemaSet schema; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileSettings.cs b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileSettings.cs new file mode 100644 index 00000000000..2a44cb79525 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/InstanceMappingFileSettings.cs @@ -0,0 +1,52 @@ +namespace NServiceBus +{ + using System; + using Configuration.AdvanceExtensibility; + using Features; + using Settings; + + /// + /// Allows configuring file-based instance mappings. + /// + public class InstanceMappingFileSettings : ExposeSettings + { + /// + /// Creates new instance of . + /// + public InstanceMappingFileSettings(SettingsHolder settings) + : base(settings) + { + } + + /// + /// Specifies the interval between data refresh attempts. + /// The default value is 30 seconds. + /// + /// Refresh interval. Valid values must be between 1 second and less than 1 day. + public InstanceMappingFileSettings RefreshInterval(TimeSpan refreshInterval) + { + if (refreshInterval < TimeSpan.FromSeconds(1)) + { + throw new ArgumentOutOfRangeException(nameof(refreshInterval), "Value must be at least 1 second."); + } + if (refreshInterval > TimeSpan.FromDays(1)) + { + throw new ArgumentOutOfRangeException(nameof(refreshInterval), "Value must be less than 1 day."); + } + Settings.Set(InstanceMappingFileFeature.CheckIntervalSettingsKey, refreshInterval); + return this; + } + + /// + /// Specifies the path and file name for the instance mapping XML. The default is instance-mapping.xml. + /// + /// The relative or absolute file path to the instance mapping XML file. + public InstanceMappingFileSettings FilePath(string filePath) + { + Guard.AgainstNullAndEmpty(nameof(filePath), filePath); + + Settings.Set(InstanceMappingFileFeature.FilePathSettingsKey, filePath); + return this; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/endpoints.xsd b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/endpoints.xsd new file mode 100644 index 00000000000..6b8b4fb649e --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/InstanceMapping/endpoints.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NServiceBus.Core/Transports/Msmq/MessagePump.cs b/src/NServiceBus.Core/Transports/Msmq/MessagePump.cs new file mode 100644 index 00000000000..f8f3946a311 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MessagePump.cs @@ -0,0 +1,242 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.Linq; + using System.Messaging; + using System.Threading; + using System.Threading.Tasks; + using Logging; + using Support; + using Transport; + + class MessagePump : IPushMessages, IDisposable + { + public MessagePump(Func receiveStrategyFactory) + { + this.receiveStrategyFactory = receiveStrategyFactory; + } + + public void Dispose() + { + // Injected + } + + + public Task Init(Func onMessage, Func> onError, CriticalError criticalError, PushSettings settings) + { + peekCircuitBreaker = new RepeatedFailuresOverTimeCircuitBreaker("MsmqPeek", TimeSpan.FromSeconds(30), ex => criticalError.Raise("Failed to peek " + settings.InputQueue, ex)); + receiveCircuitBreaker = new RepeatedFailuresOverTimeCircuitBreaker("MsmqReceive", TimeSpan.FromSeconds(30), ex => criticalError.Raise("Failed to receive from " + settings.InputQueue, ex)); + + var inputAddress = MsmqAddress.Parse(settings.InputQueue); + var errorAddress = MsmqAddress.Parse(settings.ErrorQueue); + + if (!string.Equals(inputAddress.Machine, RuntimeEnvironment.MachineName, StringComparison.OrdinalIgnoreCase)) + { + throw new Exception($"MSMQ Dequeuing can only run against the local machine. Invalid inputQueue name '{settings.InputQueue}'."); + } + + inputQueue = new MessageQueue(inputAddress.FullPath, false, true, QueueAccessMode.Receive); + errorQueue = new MessageQueue(errorAddress.FullPath, false, true, QueueAccessMode.Send); + + if (settings.RequiredTransactionMode != TransportTransactionMode.None && !QueueIsTransactional()) + { + throw new ArgumentException($"Queue must be transactional if you configure the endpoint to be transactional ({settings.InputQueue})."); + } + + inputQueue.MessageReadPropertyFilter = DefaultReadPropertyFilter; + + if (settings.PurgeOnStartup) + { + inputQueue.Purge(); + } + + receiveStrategy = receiveStrategyFactory(settings.RequiredTransactionMode); + + receiveStrategy.Init(inputQueue, errorQueue, onMessage, onError, criticalError); + + return TaskEx.CompletedTask; + } + + public void Start(PushRuntimeSettings limitations) + { + MessageQueue.ClearConnectionCache(); + + runningReceiveTasks = new ConcurrentDictionary(); + concurrencyLimiter = new SemaphoreSlim(limitations.MaxConcurrency); + cancellationTokenSource = new CancellationTokenSource(); + + cancellationToken = cancellationTokenSource.Token; + // ReSharper disable once ConvertClosureToMethodGroup + // LongRunning is useless combined with async/await + messagePumpTask = Task.Run(() => ProcessMessages(), CancellationToken.None); + } + + public async Task Stop() + { + cancellationTokenSource.Cancel(); + + // ReSharper disable once MethodSupportsCancellation + var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30)); + var allTasks = runningReceiveTasks.Values.Concat(new[] + { + messagePumpTask + }); + var finishedTask = await Task.WhenAny(Task.WhenAll(allTasks), timeoutTask).ConfigureAwait(false); + + if (finishedTask.Equals(timeoutTask)) + { + Logger.Error("The message pump failed to stop with in the time allowed(30s)"); + } + + concurrencyLimiter.Dispose(); + runningReceiveTasks.Clear(); + inputQueue.Dispose(); + errorQueue.Dispose(); + } + + [DebuggerNonUserCode] + async Task ProcessMessages() + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + await InnerProcessMessages().ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // For graceful shutdown purposes + } + catch (Exception ex) + { + Logger.Error("MSMQ Message pump failed", ex); + await peekCircuitBreaker.Failure(ex).ConfigureAwait(false); + } + } + } + + async Task InnerProcessMessages() + { + using (var enumerator = inputQueue.GetMessageEnumerator2()) + { + while (!cancellationTokenSource.IsCancellationRequested) + { + try + { + //note: .Peek will throw an ex if no message is available. It also turns out that .MoveNext is faster since message isn't read + if (!enumerator.MoveNext(TimeSpan.FromMilliseconds(10))) + { + continue; + } + + peekCircuitBreaker.Success(); + } + catch (Exception ex) + { + Logger.Warn("MSMQ receive operation failed", ex); + await peekCircuitBreaker.Failure(ex).ConfigureAwait(false); + continue; + } + + if (cancellationTokenSource.IsCancellationRequested) + { + return; + } + + await concurrencyLimiter.WaitAsync(cancellationToken).ConfigureAwait(false); + + var receiveTask = ReceiveMessage(); + + runningReceiveTasks.TryAdd(receiveTask, receiveTask); + + // We insert the original task into the runningReceiveTasks because we want to await the completion + // of the running receives. ExecuteSynchronously is a request to execute the continuation as part of + // the transition of the antecedents completion phase. This means in most of the cases the continuation + // will be executed during this transition and the antecedent task goes into the completion state only + // after the continuation is executed. This is not always the case. When the TPL thread handling the + // antecedent task is aborted the continuation will be scheduled. But in this case we don't need to await + // the continuation to complete because only really care about the receive operations. The final operation + // when shutting down is a clear of the running tasks anyway. + receiveTask.ContinueWith((t, state) => + { + var receiveTasks = (ConcurrentDictionary) state; + Task toBeRemoved; + receiveTasks.TryRemove(t, out toBeRemoved); + }, runningReceiveTasks, TaskContinuationOptions.ExecuteSynchronously) + .Ignore(); + } + } + } + + Task ReceiveMessage() + { + return TaskEx.Run(async state => + { + var messagePump = (MessagePump) state; + + try + { + await messagePump.receiveStrategy.ReceiveMessage().ConfigureAwait(false); + messagePump.receiveCircuitBreaker.Success(); + } + catch (OperationCanceledException) + { + // Intentionally ignored + } + catch (Exception ex) + { + Logger.Warn("MSMQ receive operation failed", ex); + await messagePump.receiveCircuitBreaker.Failure(ex).ConfigureAwait(false); + } + finally + { + messagePump.concurrencyLimiter.Release(); + } + }, this); + } + + bool QueueIsTransactional() + { + try + { + return inputQueue.Transactional; + } + catch (Exception ex) + { + var error = $"There is a problem with the input inputQueue: {inputQueue.Path}. See the enclosed exception for details."; + throw new InvalidOperationException(error, ex); + } + } + + CancellationToken cancellationToken; + CancellationTokenSource cancellationTokenSource; + SemaphoreSlim concurrencyLimiter; + MessageQueue errorQueue; + MessageQueue inputQueue; + + Task messagePumpTask; + + ReceiveStrategy receiveStrategy; + + RepeatedFailuresOverTimeCircuitBreaker peekCircuitBreaker; + RepeatedFailuresOverTimeCircuitBreaker receiveCircuitBreaker; + Func receiveStrategyFactory; + ConcurrentDictionary runningReceiveTasks; + + static ILog Logger = LogManager.GetLogger(); + + static MessagePropertyFilter DefaultReadPropertyFilter = new MessagePropertyFilter + { + Body = true, + TimeToBeReceived = true, + Recoverable = true, + Id = true, + ResponseQueue = true, + CorrelationId = true, + Extension = true, + AppSpecific = true + }; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqAddress.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqAddress.cs new file mode 100644 index 00000000000..a91ccfd566b --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqAddress.cs @@ -0,0 +1,108 @@ +namespace NServiceBus +{ + using System; + using System.Net; + using Support; + + struct MsmqAddress + { + public readonly string Queue; + + public readonly string Machine; + + public static MsmqAddress Parse(string address) + { + Guard.AgainstNullAndEmpty(nameof(address), address); + + var split = address.Split('@'); + + if (split.Length > 2) + { + var message = $"Address contains multiple @ characters. Address supplied: '{address}'"; + throw new ArgumentException(message, nameof(address)); + } + + var queue = split[0]; + if (string.IsNullOrWhiteSpace(queue)) + { + var message = $"Empty queue part of address. Address supplied: '{address}'"; + throw new ArgumentException(message, nameof(address)); + } + + string machineName; + if (split.Length == 2) + { + machineName = split[1]; + if (string.IsNullOrWhiteSpace(machineName)) + { + var message = $"Empty machine part of address. Address supplied: '{address}'"; + throw new ArgumentException(message, nameof(address)); + } + machineName = ApplyLocalMachineConventions(machineName); + } + else + { + machineName = RuntimeEnvironment.MachineName; + } + + return new MsmqAddress(queue, machineName); + } + + public bool IsRemote => Machine != RuntimeEnvironment.MachineName; + + static string ApplyLocalMachineConventions(string machineName) + { + if ( + machineName == "." || + machineName.ToLower() == "localhost" || + machineName == IPAddress.Loopback.ToString() + ) + { + return RuntimeEnvironment.MachineName; + } + return machineName; + } + + public MsmqAddress(string queueName, string machineName) + { + Queue = queueName; + Machine = machineName; + } + + public MsmqAddress MakeCompatibleWith(MsmqAddress other, Func ipLookup) + { + IPAddress _; + if (IPAddress.TryParse(other.Machine, out _) && !IPAddress.TryParse(Machine, out _)) + { + return new MsmqAddress(Queue, ipLookup(Machine)); + } + return this; + } + + public string FullPath + { + get + { + IPAddress ipAddress; + if (IPAddress.TryParse(Machine, out ipAddress)) + { + return PREFIX_TCP + PathWithoutPrefix; + } + return PREFIX + PathWithoutPrefix; + } + } + + public string PathWithoutPrefix => Machine + PRIVATE + Queue; + + public override string ToString() + { + return Queue + "@" + Machine; + } + + const string DIRECTPREFIX_TCP = "DIRECT=TCP:"; + const string PREFIX_TCP = "FormatName:" + DIRECTPREFIX_TCP; + const string PREFIX = "FormatName:" + DIRECTPREFIX; + const string DIRECTPREFIX = "DIRECT=OS:"; + const string PRIVATE = "\\private$\\"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqConfigurationExtensions.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqConfigurationExtensions.cs new file mode 100644 index 00000000000..4911fa84183 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqConfigurationExtensions.cs @@ -0,0 +1,63 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using System.Transactions; + using Routing; + + /// + /// Adds extensions methods to for configuration purposes. + /// + public static class MsmqConfigurationExtensions + { + /// + /// Set a delegate to use for applying the property when sending a message. + /// + /// + /// This delegate will be used for all valid messages sent via MSMQ. + /// This includes, not just standard messages, but also Audits, Errors and all control messages. + /// In some cases it may be useful to use the key to determine if a message is + /// a control message. + /// The only exception to this rule is received messages with corrupted headers. These messages will be forwarded to the + /// error queue with no label applied. + /// + public static TransportExtensions ApplyLabelToMessages(this TransportExtensions transportExtensions, Func, string> labelGenerator) + { + Guard.AgainstNull(nameof(labelGenerator), labelGenerator); + transportExtensions.Settings.Set("msmqLabelGenerator", labelGenerator); + return transportExtensions; + } + + /// + /// Allows to change the transaction isolation level and timeout for the `TransactionScope` used to receive messages. + /// + /// + /// If not specified the default transaction timeout of the machine will be used and the isolation level will be set to + /// `ReadCommited`. + /// + public static TransportExtensions TransactionScopeOptions(this TransportExtensions transportExtensions, TimeSpan? timeout = null, IsolationLevel? isolationLevel = null) + { + transportExtensions.Settings.Set(new MsmqScopeOptions(timeout, isolationLevel)); + return transportExtensions; + } + + /// + /// Sets a distribution strategy for a given endpoint. + /// + /// Config object. + /// The instance of a distribution strategy. + public static void SetMessageDistributionStrategy(this RoutingSettings config, DistributionStrategy distributionStrategy) + { + config.Settings.GetOrCreate().SetDistributionStrategy(distributionStrategy); + } + + /// + /// Returns the configuration options for the file based instance mapping file. + /// + public static InstanceMappingFileSettings InstanceMappingFile(this RoutingSettings config) + { + return new InstanceMappingFileSettings(config.Settings); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Config/MsmqConnectionStringBuilder.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqConnectionStringBuilder.cs similarity index 94% rename from src/NServiceBus.Core/Config/MsmqConnectionStringBuilder.cs rename to src/NServiceBus.Core/Transports/Msmq/MsmqConnectionStringBuilder.cs index c2251d92d2a..7064bdb9abf 100644 --- a/src/NServiceBus.Core/Config/MsmqConnectionStringBuilder.cs +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqConnectionStringBuilder.cs @@ -1,8 +1,7 @@ -namespace NServiceBus.Config +namespace NServiceBus { using System; using System.Data.Common; - using NServiceBus.Transports.Msmq.Config; class MsmqConnectionStringBuilder : DbConnectionStringBuilder { diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqDequeueStrategy.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqDequeueStrategy.cs deleted file mode 100644 index 1a4d7ac17fd..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/MsmqDequeueStrategy.cs +++ /dev/null @@ -1,398 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - using System.Diagnostics; - using System.Messaging; - using System.Security.Principal; - using System.Threading; - using System.Threading.Tasks; - using System.Transactions; - using Janitor; - using NServiceBus.CircuitBreakers; - using NServiceBus.Logging; - using NServiceBus.Support; - using NServiceBus.Unicast.Transport; - - /// - /// Default implementation of for MSMQ. - /// - public class MsmqDequeueStrategy : IDequeueMessages, IDisposable - { - /// - /// Creates an instance of . - /// - /// Configure - /// CriticalError - /// MsmqUnitOfWork - public MsmqDequeueStrategy(Configure configure, CriticalError criticalError, MsmqUnitOfWork unitOfWork) - { - this.configure = configure; - this.criticalError = criticalError; - this.unitOfWork = unitOfWork; - } - - /// - /// Initializes the . - /// - /// The address to listen on. - /// The to be used by . - /// Called when a message has been dequeued and is ready for processing. - /// - /// Needs to be called by after the message has been - /// processed regardless if the outcome was successful or not. - /// - public void Init(Address address, TransactionSettings settings, Func tryProcessMessage, - Action endProcessMessage) - { - this.tryProcessMessage = tryProcessMessage; - this.endProcessMessage = endProcessMessage; - transactionSettings = settings; - - if (address == null) - { - throw new ArgumentException("Input queue must be specified"); - } - - if (!address.Machine.Equals(RuntimeEnvironment.MachineName, StringComparison.OrdinalIgnoreCase)) - { - var error = string.Format("Input queue [{0}] must be on the same machine as this process [{1}].", address, RuntimeEnvironment.MachineName); - throw new InvalidOperationException(error); - } - - transactionOptions = new TransactionOptions - { - IsolationLevel = transactionSettings.IsolationLevel, - Timeout = transactionSettings.TransactionTimeout - }; - - queue = new MessageQueue(NServiceBus.MsmqUtilities.GetFullPath(address), false, true, QueueAccessMode.Receive); - errorQueue = new MessageQueue(NServiceBus.MsmqUtilities.GetFullPath(ErrorQueue), false, true, QueueAccessMode.Send); - - if (transactionSettings.IsTransactional && !QueueIsTransactional()) - { - throw new ArgumentException( - "Queue must be transactional if you configure your endpoint to be transactional (" + address + ")."); - } - - var messageReadPropertyFilter = new MessagePropertyFilter - { - Body = true, - TimeToBeReceived = true, - Recoverable = true, - Id = true, - ResponseQueue = true, - CorrelationId = true, - Extension = true, - AppSpecific = true - }; - - queue.MessageReadPropertyFilter = messageReadPropertyFilter; - - if (configure.PurgeOnStartup()) - { - queue.Purge(); - } - } - - /// - /// The address of the configured error queue. - /// - public Address ErrorQueue { get; set; } - - /// - /// Starts the dequeuing of message using the specified . - /// - /// The maximum concurrency level supported. - public void Start(int maximumConcurrencyLevel) - { - MessageQueue.ClearConnectionCache(); - - this.maximumConcurrencyLevel = maximumConcurrencyLevel; - throttlingSemaphore = new SemaphoreSlim(maximumConcurrencyLevel, maximumConcurrencyLevel); - - queue.PeekCompleted += OnPeekCompleted; - - CallPeekWithExceptionHandling(() => queue.BeginPeek()); - } - - /// - /// Stops the dequeuing of messages. - /// - public void Stop() - { - queue.PeekCompleted -= OnPeekCompleted; - - stopResetEvent.WaitOne(); - DrainStopSemaphore(); - queue.Dispose(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - // Injected - } - - void DrainStopSemaphore() - { - Logger.Debug("Drain stopping 'Throttling Semaphore'."); - for (var index = 0; index < maximumConcurrencyLevel; index++) - { - Logger.Debug(string.Format("Claiming Semaphore thread {0}/{1}.", index + 1, maximumConcurrencyLevel)); - throttlingSemaphore.Wait(); - } - Logger.Debug("Releasing all claimed Semaphore threads."); - throttlingSemaphore.Release(maximumConcurrencyLevel); - - throttlingSemaphore.Dispose(); - } - - bool QueueIsTransactional() - { - try - { - return queue.Transactional; - } - catch (Exception ex) - { - var error = string.Format("There is a problem with the input queue: {0}. See the enclosed exception for details.", queue.Path); - throw new InvalidOperationException(error, ex); - } - } - - void OnPeekCompleted(object sender, PeekCompletedEventArgs peekCompletedEventArgs) - { - stopResetEvent.Reset(); - - CallPeekWithExceptionHandling(() => queue.EndPeek(peekCompletedEventArgs.AsyncResult)); - - throttlingSemaphore.Wait(); - - Task.Factory - .StartNew(Action, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default) - .ContinueWith(task => task.Exception.Handle(ex => - { - Logger.Error("Error processing message.", ex); - return true; - }), TaskContinuationOptions.OnlyOnFaulted); - - //We using an AutoResetEvent here to make sure we do not call another BeginPeek before the Receive has been called - peekResetEvent.WaitOne(); - - CallPeekWithExceptionHandling(() => queue.BeginPeek()); - - stopResetEvent.Set(); - } - - void Action() - { - TransportMessage transportMessage = null; - try - { - if (transactionSettings.IsTransactional) - { - if (transactionSettings.SuppressDistributedTransactions) - { - using (var msmqTransaction = new MessageQueueTransaction()) - { - msmqTransaction.Begin(); - - Message message; - - if (!TryReceiveMessage(() => queue.Receive(receiveTimeout, msmqTransaction),out message)) - { - msmqTransaction.Commit(); - return; - } - - try - { - unitOfWork.SetTransaction(msmqTransaction); - - try - { - transportMessage = NServiceBus.MsmqUtilities.Convert(message); - } - catch (Exception exception) - { - LogCorruptedMessage(message, exception); - errorQueue.Send(message, msmqTransaction); - msmqTransaction.Commit(); - return; - } - - if (tryProcessMessage(transportMessage)) - { - msmqTransaction.Commit(); - } - else - { - msmqTransaction.Abort(); - } - } - finally - { - unitOfWork.ClearTransaction(); - } - } - } - else - { - using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions)) - { - Message message; - - if (!TryReceiveMessage(() => queue.Receive(receiveTimeout, MessageQueueTransactionType.Automatic),out message)) - { - scope.Complete(); - return; - } - - try - { - transportMessage = NServiceBus.MsmqUtilities.Convert(message); - } - catch (Exception ex) - { - LogCorruptedMessage(message, ex); - errorQueue.Send(message, MessageQueueTransactionType.Automatic); - scope.Complete(); - return; - } - - if (tryProcessMessage(transportMessage)) - { - scope.Complete(); - } - } - } - } - else - { - Message message; - - if (!TryReceiveMessage(() => queue.Receive(receiveTimeout, MessageQueueTransactionType.None),out message)) - { - return; - } - - try - { - transportMessage = NServiceBus.MsmqUtilities.Convert(message); - } - catch (Exception exception) - { - LogCorruptedMessage(message, exception); - errorQueue.Send(message, MessageQueueTransactionType.None); - return; - } - - tryProcessMessage(transportMessage); - } - - endProcessMessage(transportMessage, null); - } - catch (Exception ex) - { - endProcessMessage(transportMessage, ex); - } - finally - { - throttlingSemaphore.Release(); - } - } - - void LogCorruptedMessage(Message message, Exception ex) - { - var error = string.Format("Message '{0}' is corrupt and will be moved to '{1}'", message.Id, ErrorQueue.Queue); - Logger.Error(error, ex); - } - - void CallPeekWithExceptionHandling(Action action) - { - try - { - action(); - } - catch (MessageQueueException messageQueueException) - { - RaiseCriticalException(messageQueueException); - } - } - [DebuggerNonUserCode] - bool TryReceiveMessage(Func receive,out Message message) - { - message = null; - - try - { - message = receive(); - return true; - } - catch (MessageQueueException messageQueueException) - { - if (messageQueueException.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) - { - //We should only get an IOTimeout exception here if another process removed the message between us peeking and now. - return false; - } - - RaiseCriticalException(messageQueueException); - } - catch (Exception ex) - { - Logger.Error("Error in receiving messages.", ex); - } - finally - { - peekResetEvent.Set(); - } - - return false; - } - - void RaiseCriticalException(MessageQueueException messageQueueException) - { - var errorException = string.Format("Failed to peek messages from [{0}].", queue.FormatName); - - if (messageQueueException.MessageQueueErrorCode == MessageQueueErrorCode.AccessDenied) - { - errorException = - string.Format( - "Do not have permission to access queue [{0}]. Make sure that the current user [{1}] has permission to Send, Receive, and Peek from this queue.", - queue.FormatName, GetUserName()); - } - - circuitBreaker.Execute(() => criticalError.Raise("Error in receiving messages.", new InvalidOperationException(errorException, messageQueueException))); - } - - static string GetUserName() - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - return windowsIdentity != null - ? windowsIdentity.Name - : "Unknown User"; - } - - static ILog Logger = LogManager.GetLogger(); - Configure configure; - CriticalError criticalError; - TimeSpan receiveTimeout = TimeSpan.FromSeconds(1); - [SkipWeaving] - MsmqUnitOfWork unitOfWork; - CircuitBreaker circuitBreaker = new CircuitBreaker(100, TimeSpan.FromSeconds(30)); - Action endProcessMessage; - int maximumConcurrencyLevel; - AutoResetEvent peekResetEvent = new AutoResetEvent(false); - MessageQueue queue; - MessageQueue errorQueue; - ManualResetEvent stopResetEvent = new ManualResetEvent(true); - SemaphoreSlim throttlingSemaphore; - TransactionOptions transactionOptions; - TransactionSettings transactionSettings; - Func tryProcessMessage; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqFailureInfoStorage.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqFailureInfoStorage.cs new file mode 100644 index 00000000000..04fa0e3bd15 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqFailureInfoStorage.cs @@ -0,0 +1,108 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Runtime.ExceptionServices; + + // The data structure has fixed maximum size. When the data structure reaches its maximum size, + // the least recently used (LRU) message processing failure is removed from the storage. + class MsmqFailureInfoStorage + { + public MsmqFailureInfoStorage(int maxElements) + { + this.maxElements = maxElements; + } + + public void RecordFailureInfoForMessage(string messageId, Exception exception) + { + lock (lockObject) + { + FailureInfoNode node; + if (failureInfoPerMessage.TryGetValue(messageId, out node)) + { + // We have seen this message before, just update the counter and store exception. + node.FailureInfo = new ProcessingFailureInfo(node.FailureInfo.NumberOfProcessingAttempts + 1, ExceptionDispatchInfo.Capture(exception)); + + // Maintain invariant: leastRecentlyUsedMessages.First contains the LRU item. + leastRecentlyUsedMessages.Remove(node.LeastRecentlyUsedEntry); + leastRecentlyUsedMessages.AddLast(node.LeastRecentlyUsedEntry); + } + else + { + if (failureInfoPerMessage.Count == maxElements) + { + // We have reached the maximum allowed capacity. Remove the LRU item. + var leastRecentlyUsedEntry = leastRecentlyUsedMessages.First; + failureInfoPerMessage.Remove(leastRecentlyUsedEntry.Value); + leastRecentlyUsedMessages.RemoveFirst(); + } + + var newNode = new FailureInfoNode( + messageId, + new ProcessingFailureInfo(1, ExceptionDispatchInfo.Capture(exception))); + + failureInfoPerMessage[messageId] = newNode; + + // Maintain invariant: leastRecentlyUsedMessages.First contains the LRU item. + leastRecentlyUsedMessages.AddLast(newNode.LeastRecentlyUsedEntry); + } + } + } + + public bool TryGetFailureInfoForMessage(string messageId, out ProcessingFailureInfo processingFailureInfo) + { + lock (lockObject) + { + FailureInfoNode node; + if (!failureInfoPerMessage.TryGetValue(messageId, out node)) + { + processingFailureInfo = null; + return false; + } + processingFailureInfo = node.FailureInfo; + + return true; + } + } + + public void ClearFailureInfoForMessage(string messageId) + { + lock (lockObject) + { + failureInfoPerMessage.Remove(messageId); + leastRecentlyUsedMessages.Remove(messageId); + } + } + + Dictionary failureInfoPerMessage = new Dictionary(); + LinkedList leastRecentlyUsedMessages = new LinkedList(); + object lockObject = new object(); + + int maxElements; + + class FailureInfoNode + { + public FailureInfoNode(string messageId, ProcessingFailureInfo failureInfo) + { + FailureInfo = failureInfo; + LeastRecentlyUsedEntry = new LinkedListNode(messageId); + } + + public ProcessingFailureInfo FailureInfo { get; set; } + public LinkedListNode LeastRecentlyUsedEntry { get; } + } + + public class ProcessingFailureInfo + { + public ProcessingFailureInfo(int numberOfProcessingAttempts, ExceptionDispatchInfo exceptionDispatchInfo) + { + NumberOfProcessingAttempts = numberOfProcessingAttempts; + ExceptionDispatchInfo = exceptionDispatchInfo; + } + + public int NumberOfProcessingAttempts { get; } + public Exception Exception => ExceptionDispatchInfo.SourceException; + ExceptionDispatchInfo ExceptionDispatchInfo { get; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqMessageDispatcher.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqMessageDispatcher.cs new file mode 100644 index 00000000000..38b28bdc59d --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqMessageDispatcher.cs @@ -0,0 +1,194 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Messaging; + using System.Threading.Tasks; + using System.Transactions; + using DeliveryConstraints; + using Extensibility; + using Performance.TimeToBeReceived; + using Transport; + using Unicast.Queuing; + + class MsmqMessageDispatcher : IDispatchMessages + { + public MsmqMessageDispatcher(MsmqSettings settings, Func, string> messageLabelGenerator) + { + Guard.AgainstNull(nameof(settings), settings); + Guard.AgainstNull(nameof(messageLabelGenerator), messageLabelGenerator); + + this.settings = settings; + this.messageLabelGenerator = messageLabelGenerator; + } + + public Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) + { + Guard.AgainstNull(nameof(outgoingMessages), outgoingMessages); + + if (outgoingMessages.MulticastTransportOperations.Any()) + { + throw new Exception("The MSMQ transport only supports unicast transport operations."); + } + + foreach (var unicastTransportOperation in outgoingMessages.UnicastTransportOperations) + { + ExecuteTransportOperation(transaction, unicastTransportOperation); + } + + return TaskEx.CompletedTask; + } + + void ExecuteTransportOperation(TransportTransaction transaction, UnicastTransportOperation transportOperation) + { + var message = transportOperation.Message; + + var destination = transportOperation.Destination; + var destinationAddress = MsmqAddress.Parse(destination); + + if (IsCombiningTimeToBeReceivedWithTransactions( + transaction, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints)) + { + throw new Exception($"Failed to send message to address: {destinationAddress.Queue}@{destinationAddress.Machine}. Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ."); + } + + try + { + using (var q = new MessageQueue(destinationAddress.FullPath, false, settings.UseConnectionCache, QueueAccessMode.Send)) + { + using (var toSend = MsmqUtilities.Convert(message, transportOperation.DeliveryConstraints)) + { + toSend.UseDeadLetterQueue = settings.UseDeadLetterQueue; + toSend.UseJournalQueue = settings.UseJournalQueue; + toSend.TimeToReachQueue = settings.TimeToReachQueue; + + string replyToAddress; + + if (message.Headers.TryGetValue(Headers.ReplyToAddress, out replyToAddress)) + { + toSend.ResponseQueue = new MessageQueue(MsmqAddress.Parse(replyToAddress).FullPath); + } + + var label = GetLabel(message); + + if (transportOperation.RequiredDispatchConsistency == DispatchConsistency.Isolated) + { + q.Send(toSend, label, GetIsolatedTransactionType()); + return; + } + + MessageQueueTransaction activeTransaction; + if (TryGetNativeTransaction(transaction, out activeTransaction)) + { + q.Send(toSend, label, activeTransaction); + return; + } + + q.Send(toSend, label, GetTransactionTypeForSend()); + } + } + } + catch (MessageQueueException ex) + { + if (ex.MessageQueueErrorCode == MessageQueueErrorCode.QueueNotFound) + { + var msg = destination == null + ? "Failed to send message. Target address is null." + : $"Failed to send message to address: [{destination}]"; + + throw new QueueNotFoundException(destination, msg, ex); + } + + ThrowFailedToSendException(destination, ex); + } + catch (Exception ex) + { + ThrowFailedToSendException(destination, ex); + } + } + + bool IsCombiningTimeToBeReceivedWithTransactions(TransportTransaction transaction, DispatchConsistency requiredDispatchConsistency, List deliveryConstraints) + { + if (!settings.UseTransactionalQueues) + { + return false; + } + + if (requiredDispatchConsistency == DispatchConsistency.Isolated) + { + return false; + } + + DiscardIfNotReceivedBefore discardIfNotReceivedBefore; + var timeToBeReceivedRequested = deliveryConstraints.TryGet(out discardIfNotReceivedBefore) && discardIfNotReceivedBefore.MaxTime < MessageQueue.InfiniteTimeout; + + if (!timeToBeReceivedRequested) + { + return false; + } + + if (Transaction.Current != null) + { + return true; + } + + MessageQueueTransaction activeReceiveTransaction; + + return TryGetNativeTransaction(transaction, out activeReceiveTransaction); + } + + static bool TryGetNativeTransaction(TransportTransaction transportTransaction, out MessageQueueTransaction transaction) + { + return transportTransaction.TryGet(out transaction); + } + + MessageQueueTransactionType GetIsolatedTransactionType() + { + return settings.UseTransactionalQueues ? MessageQueueTransactionType.Single : MessageQueueTransactionType.None; + } + + string GetLabel(OutgoingMessage message) + { + var messageLabel = messageLabelGenerator(new ReadOnlyDictionary(message.Headers)); + if (messageLabel == null) + { + throw new Exception("MSMQ label convention returned a null. Either return a valid value or a String.Empty to indicate 'no value'."); + } + if (messageLabel.Length > 240) + { + throw new Exception("MSMQ label convention returned a value longer than 240 characters. This is not supported."); + } + return messageLabel; + } + + static void ThrowFailedToSendException(string address, Exception ex) + { + if (address == null) + { + throw new Exception("Failed to send message.", ex); + } + + throw new Exception($"Failed to send message to address: {address}", ex); + } + + MessageQueueTransactionType GetTransactionTypeForSend() + { + if (!settings.UseTransactionalQueues) + { + return MessageQueueTransactionType.None; + } + + return Transaction.Current != null + ? MessageQueueTransactionType.Automatic + : MessageQueueTransactionType.Single; + } + + Func, string> messageLabelGenerator; + + MsmqSettings settings; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqMessageSender.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqMessageSender.cs deleted file mode 100644 index 81f2fafe835..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/MsmqMessageSender.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - using System.Messaging; - using System.Transactions; - using Config; - using Unicast; - using Unicast.Queuing; - - /// - /// Default MSMQ implementation. - /// - public class MsmqMessageSender : ISendMessages - { - /// - /// MsmqSettings - /// - public MsmqSettings Settings { get; set; } - - /// - /// MsmqUnitOfWork - /// - public MsmqUnitOfWork UnitOfWork { get; set; } - - /// - /// SuppressDistributedTransactions - /// - public bool SuppressDistributedTransactions { get; set; } - - /// - /// Sends the given - /// - public void Send(TransportMessage message, SendOptions sendOptions) - { - var address = sendOptions.Destination; - var queuePath = NServiceBus.MsmqUtilities.GetFullPath(address); - - var transactionType = GetTransactionTypeForSend(); - if (message.TimeToBeReceived < MessageQueue.InfiniteTimeout && WillUseTransactionThatSupportsMultipleOperations(sendOptions.EnlistInReceiveTransaction, transactionType)) - { - throw new Exception($"Failed to send message to address: {address.Queue}@{address.Machine}. Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ."); - } - - try - { - using (var q = new MessageQueue(queuePath, false, Settings.UseConnectionCache, QueueAccessMode.Send)) - { - using (var toSend = NServiceBus.MsmqUtilities.Convert(message)) - { - toSend.UseDeadLetterQueue = Settings.UseDeadLetterQueue; - toSend.UseJournalQueue = Settings.UseJournalQueue; - toSend.TimeToReachQueue = Settings.TimeToReachQueue; - - var replyToAddress = sendOptions.ReplyToAddress ?? message.ReplyToAddress; - - if (replyToAddress != null) - { - toSend.ResponseQueue = new MessageQueue(NServiceBus.MsmqUtilities.GetReturnAddress(replyToAddress.ToString(), address.ToString())); - } - - if (sendOptions.EnlistInReceiveTransaction && UnitOfWork.HasActiveTransaction()) - { - q.Send(toSend, UnitOfWork.Transaction); - } - else - { - q.Send(toSend, transactionType); - } - } - } - } - catch (MessageQueueException ex) - { - if (ex.MessageQueueErrorCode == MessageQueueErrorCode.QueueNotFound) - { - var msg = address == null - ? "Failed to send message. Target address is null." - : string.Format("Failed to send message to address: [{0}]", address); - - throw new QueueNotFoundException(address, msg, ex); - } - - ThrowFailedToSendException(address, ex); - } - catch (Exception ex) - { - ThrowFailedToSendException(address, ex); - } - } - - bool WillUseTransactionThatSupportsMultipleOperations(bool enlistInReceiveTransaction, MessageQueueTransactionType transactionType) - { - var willUseReceiveTransaction = enlistInReceiveTransaction && UnitOfWork.HasActiveTransaction(); - var willUseAutomaticTransaction = transactionType == MessageQueueTransactionType.Automatic; - return willUseReceiveTransaction || willUseAutomaticTransaction; - } - - static void ThrowFailedToSendException(Address address, Exception ex) - { - if (address == null) - throw new Exception("Failed to send message.", ex); - - throw new Exception( - string.Format("Failed to send message to address: {0}@{1}", address.Queue, address.Machine), ex); - } - - MessageQueueTransactionType GetTransactionTypeForSend() - { - if (!Settings.UseTransactionalQueues) - { - return MessageQueueTransactionType.None; - } - - return Transaction.Current != null - ? MessageQueueTransactionType.Automatic - : MessageQueueTransactionType.Single; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqQueueCreator.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqQueueCreator.cs deleted file mode 100644 index 5b3692326e6..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/MsmqQueueCreator.cs +++ /dev/null @@ -1,114 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - using System.Messaging; - using System.Security.Principal; - using Config; - using Logging; - using Support; - - class MsmqQueueCreator : ICreateQueues - { - static ILog Logger = LogManager.GetLogger(); - static string LocalAdministratorsGroupName = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)).ToString(); - static string LocalEveryoneGroupName = new SecurityIdentifier(WellKnownSidType.WorldSid, null).Translate(typeof(NTAccount)).ToString(); - static string LocalAnonymousLogonName = new SecurityIdentifier(WellKnownSidType.AnonymousSid, null).Translate(typeof(NTAccount)).ToString(); - - /// - /// The current runtime settings - /// - public MsmqSettings Settings { get; set; } - - /// - /// Utility method for creating a queue if it does not exist. - /// - ///Queue path to create - ///The account to be given permissions to the queue - public void CreateQueueIfNecessary(Address address, string account) - { - if (address == null) - { - return; - } - - var queuePath = GetFullPathWithoutPrefix(address); - var isRemote = address.Machine.ToLower() != RuntimeEnvironment.MachineName.ToLower(); - - if (isRemote) - { - Logger.Debug("Queue is on remote machine."); - Logger.Debug("If this does not succeed (like if the remote machine is disconnected), processing will continue."); - } - - Logger.Debug(String.Format("Checking if queue exists: {0}.", address)); - - try - { - if (MessageQueue.Exists(queuePath)) - { - Logger.Debug("Queue exists, going to set permissions."); - SetPermissionsForQueue(queuePath, account); - return; - } - - Logger.Warn("Queue " + queuePath + " does not exist."); - Logger.Debug("Going to create queue: " + queuePath); - - CreateQueue(queuePath, account, Settings.UseTransactionalQueues); - } - catch (MessageQueueException ex) - { - if (isRemote && (ex.MessageQueueErrorCode == MessageQueueErrorCode.IllegalQueuePathName)) - { - return; - } - Logger.Error(String.Format("Could not create queue {0} or check its existence. Processing will still continue.", address), ex); - } - catch (Exception ex) - { - Logger.Error(String.Format("Could not create queue {0} or check its existence. Processing will still continue.", address), ex); - } - } - - /// - /// Returns the full path without Format or direct os - /// from an address. - /// - public static string GetFullPathWithoutPrefix(Address address) - { - return address.Machine + NServiceBus.MsmqUtilities.PRIVATE + address.Queue; - } - - static void CreateQueue(string queuePath, string account, bool transactional) - { - using (var queue = MessageQueue.Create(queuePath, transactional)) - { - SetPermissionsForQueue(queue, account); - } - - Logger.DebugFormat("Created queue, path: [{0}], account: [{1}], transactional: [{2}]", queuePath, account, transactional); - } - - static void SetPermissionsForQueue(string queuePath, string account) - { - using (var messageQueue = new MessageQueue(queuePath)) - { - SetPermissionsForQueue(messageQueue, account); - } - } - - /// - /// Sets default permissions for queue. - /// - static void SetPermissionsForQueue(MessageQueue queue, string account) - { - queue.SetPermissions(LocalAdministratorsGroupName, MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow); - queue.SetPermissions(LocalEveryoneGroupName, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); - queue.SetPermissions(LocalAnonymousLogonName, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); - - queue.SetPermissions(account, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); - queue.SetPermissions(account, MessageQueueAccessRights.ReceiveMessage, AccessControlEntryType.Allow); - queue.SetPermissions(account, MessageQueueAccessRights.PeekMessage, AccessControlEntryType.Allow); - } - } -} diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqScopeOptions.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqScopeOptions.cs new file mode 100644 index 00000000000..f54eedef496 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqScopeOptions.cs @@ -0,0 +1,59 @@ +namespace NServiceBus +{ + using System; + using System.Configuration; + using System.Transactions; + using System.Transactions.Configuration; + + class MsmqScopeOptions + { + public MsmqScopeOptions(TimeSpan? requestedTimeout = null, IsolationLevel? requestedIsolationLevel = null) + { + var timeout = TransactionManager.DefaultTimeout; + var isolationLevel = IsolationLevel.ReadCommitted; + if (requestedTimeout.HasValue) + { + var maxTimeout = GetMaxTimeout(); + + if (requestedTimeout.Value > maxTimeout) + { + throw new ConfigurationErrorsException( + "Timeout requested is longer than the maximum value for this machine. Override using the maxTimeout setting of the system.transactions section in machine.config"); + } + + timeout = requestedTimeout.Value; + } + + if (requestedIsolationLevel.HasValue) + { + isolationLevel = requestedIsolationLevel.Value; + } + + TransactionOptions = new TransactionOptions + { + IsolationLevel = isolationLevel, + Timeout = timeout + }; + } + + public TransactionOptions TransactionOptions { get; } + + static TimeSpan GetMaxTimeout() + { + //default is always 10 minutes + var maxTimeout = TimeSpan.FromMinutes(10); + + var systemTransactionsGroup = ConfigurationManager.OpenMachineConfiguration() + .GetSectionGroup("system.transactions"); + + var machineSettings = systemTransactionsGroup?.Sections.Get("machineSettings") as MachineSettingsSection; + + if (machineSettings != null) + { + maxTimeout = machineSettings.MaxTimeout; + } + + return maxTimeout; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqSettings.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqSettings.cs new file mode 100644 index 00000000000..161d4d31ff9 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqSettings.cs @@ -0,0 +1,26 @@ +namespace NServiceBus +{ + using System; + using System.Messaging; + + class MsmqSettings + { + public MsmqSettings() + { + UseDeadLetterQueue = true; + UseConnectionCache = true; + UseTransactionalQueues = true; + TimeToReachQueue = Message.InfiniteTimeout; + } + + public bool UseDeadLetterQueue { get; set; } + + public bool UseJournalQueue { get; set; } + + public bool UseConnectionCache { get; set; } + + public bool UseTransactionalQueues { get; set; } + + public TimeSpan TimeToReachQueue { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqTimeToBeReceivedOverrideCheck.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqTimeToBeReceivedOverrideCheck.cs new file mode 100644 index 00000000000..6f062e159ab --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqTimeToBeReceivedOverrideCheck.cs @@ -0,0 +1,34 @@ +namespace NServiceBus +{ + using System; + using Config; + using ConsistencyGuarantees; + using Features; + using Settings; + using Transport; + + class MsmqTimeToBeReceivedOverrideCheck + { + public MsmqTimeToBeReceivedOverrideCheck(ReadOnlySettings settings) + { + this.settings = settings; + } + + public StartupCheckResult CheckTimeToBeReceivedOverrides() + { + var usingMsmq = settings.Get() is MsmqTransport; + var isTransactional = settings.GetRequiredTransactionModeForReceives() != TransportTransactionMode.None; + var outBoxRunning = settings.IsFeatureActive(typeof(Features.Outbox)); + + var messageAuditingConfig = settings.GetConfigSection(); + var auditTTBROverridden = messageAuditingConfig != null && messageAuditingConfig.OverrideTimeToBeReceived > TimeSpan.Zero; + + var unicastBusConfig = settings.GetConfigSection(); + var forwardTTBROverridden = unicastBusConfig != null && unicastBusConfig.TimeToBeReceivedOnForwardedMessages > TimeSpan.Zero; + + return TimeToBeReceivedOverrideChecker.Check(usingMsmq, isTransactional, outBoxRunning, auditTTBROverridden, forwardTTBROverridden); + } + + ReadOnlySettings settings; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqTransport.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqTransport.cs new file mode 100644 index 00000000000..0d1cbb4a14d --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqTransport.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using Features; + using Routing; + using Settings; + using Transport; + + /// + /// Transport definition for MSMQ. + /// + public class MsmqTransport : TransportDefinition, IMessageDrivenSubscriptionTransport + { + /// + /// . + /// + public override string ExampleConnectionStringForErrorMessage => "cacheSendConnection=true;journal=false;deadLetter=true"; + + /// + /// . + /// + public override bool RequiresConnectionString => false; + + /// + /// Initializes the transport infrastructure for msmq. + /// + /// The settings. + /// The connection string. + /// the transport infrastructure for msmq. + public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) + { + settings.EnableFeature(typeof(InstanceMappingFileFeature)); + + return new MsmqTransportInfrastructure(settings, connectionString); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqTransportInfrastructure.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqTransportInfrastructure.cs new file mode 100644 index 00000000000..5e6494595a2 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqTransportInfrastructure.cs @@ -0,0 +1,149 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Threading.Tasks; + using System.Transactions; + using Features; + using Performance.TimeToBeReceived; + using Routing; + using Settings; + using Support; + using Transport; + + class MsmqTransportInfrastructure : TransportInfrastructure + { + public MsmqTransportInfrastructure(ReadOnlySettings settings, string connectionString) + { + RequireOutboxConsent = true; + + this.settings = settings; + this.connectionString = connectionString; + } + + public override IEnumerable DeliveryConstraints { get; } = new[] + { + typeof(DiscardIfNotReceivedBefore), + typeof(NonDurableDelivery) + }; + + public override TransportTransactionMode TransactionMode { get; } = TransportTransactionMode.TransactionScope; + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + + ReceiveStrategy SelectReceiveStrategy(TransportTransactionMode minimumConsistencyGuarantee, TransactionOptions transactionOptions) + { + if (minimumConsistencyGuarantee == TransportTransactionMode.TransactionScope) + { + return new ReceiveWithTransactionScope(transactionOptions, new MsmqFailureInfoStorage(1000)); + } + + if (minimumConsistencyGuarantee == TransportTransactionMode.None) + { + return new ReceiveWithNoTransaction(); + } + + return new ReceiveWithNativeTransaction(new MsmqFailureInfoStorage(1000)); + } + + public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) => instance.AtMachine(RuntimeEnvironment.MachineName); + + public override string ToTransportAddress(LogicalAddress logicalAddress) + { + string machine; + if (!logicalAddress.EndpointInstance.Properties.TryGetValue("machine", out machine)) + { + machine = RuntimeEnvironment.MachineName; + } + string queueName; + if (!logicalAddress.EndpointInstance.Properties.TryGetValue("queue", out queueName)) + { + queueName = logicalAddress.EndpointInstance.Endpoint; + } + var queue = new StringBuilder(queueName); + if (logicalAddress.EndpointInstance.Discriminator != null) + { + queue.Append("-" + logicalAddress.EndpointInstance.Discriminator); + } + if (logicalAddress.Qualifier != null) + { + queue.Append("." + logicalAddress.Qualifier); + } + return queue + "@" + machine; + } + + public override string MakeCanonicalForm(string transportAddress) + { + return MsmqAddress.Parse(transportAddress).ToString(); + } + + public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() + { + new CheckMachineNameForComplianceWithDtcLimitation().Check(); + + var builder = connectionString != null + ? new MsmqConnectionStringBuilder(connectionString).RetrieveSettings() + : new MsmqSettings(); + + MsmqScopeOptions scopeOptions; + + if (!settings.TryGet(out scopeOptions)) + { + scopeOptions = new MsmqScopeOptions(); + } + + return new TransportReceiveInfrastructure( + () => new MessagePump(guarantee => SelectReceiveStrategy(guarantee, scopeOptions.TransactionOptions)), + () => new QueueCreator(builder), + () => + { + var bindings = settings.Get(); + + foreach (var address in bindings.ReceivingAddresses) + { + QueuePermissions.CheckQueue(address); + } + return Task.FromResult(StartupCheckResult.Success); + }); + } + + public override TransportSendInfrastructure ConfigureSendInfrastructure() + { + new CheckMachineNameForComplianceWithDtcLimitation().Check(); + + Func, string> getMessageLabel; + settings.TryGet("Msmq.GetMessageLabel", out getMessageLabel); + + Func, string> messageLabelGenerator; + if (!settings.TryGet("msmqLabelGenerator", out messageLabelGenerator)) + { + messageLabelGenerator = headers => string.Empty; + } + + var builder = new MsmqConnectionStringBuilder(connectionString).RetrieveSettings(); + + return new TransportSendInfrastructure( + () => new MsmqMessageDispatcher(builder, messageLabelGenerator), + () => + { + var bindings = settings.Get(); + + foreach (var address in bindings.SendingAddresses) + { + QueuePermissions.CheckQueue(address); + } + + var result = new MsmqTimeToBeReceivedOverrideCheck(settings).CheckTimeToBeReceivedOverrides(); + return Task.FromResult(result); + }); + } + + public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() + { + throw new NotImplementedException("MSMQ does not support native pub/sub."); + } + + string connectionString; + ReadOnlySettings settings; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqUnitOfWork.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqUnitOfWork.cs deleted file mode 100644 index a76a9305254..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/MsmqUnitOfWork.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - using System.Messaging; - using System.Threading; - - /// - /// Msmq unit of work to be used in non DTC mode. - /// - public class MsmqUnitOfWork : IDisposable - { - /// - /// Current . - /// - public MessageQueueTransaction Transaction - { - get { return currentTransaction.Value; } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - //Injected - } - - internal void SetTransaction(MessageQueueTransaction msmqTransaction) - { - currentTransaction.Value = msmqTransaction; - } - - /// - /// Checks whether a exists. - /// - /// true if a is currently in progress, otherwise false. - public bool HasActiveTransaction() - { - return currentTransaction.IsValueCreated; - } - - internal void ClearTransaction() - { - currentTransaction.Value = null; - } - - ThreadLocal currentTransaction = new ThreadLocal(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/MsmqUtilities.cs b/src/NServiceBus.Core/Transports/Msmq/MsmqUtilities.cs index d60f8f2d250..07a61e0fba4 100644 --- a/src/NServiceBus.Core/Transports/Msmq/MsmqUtilities.cs +++ b/src/NServiceBus.Core/Transports/Msmq/MsmqUtilities.cs @@ -5,118 +5,31 @@ namespace NServiceBus using System.IO; using System.Linq; using System.Messaging; - using System.Net; - using System.Net.NetworkInformation; using System.Text; using System.Xml; + using DeliveryConstraints; using Logging; - using Transports.Msmq; + using Performance.TimeToBeReceived; + using Transport; + using Transport.Msmq; - /// - /// MSMQ-related utility functions - /// class MsmqUtilities { - /// - /// Turns a '@' separated value into a full path. - /// Format is 'queue@machine', or 'queue@ipaddress' - /// - public static string GetFullPath(Address value) + static MsmqAddress GetIndependentAddressForQueue(MessageQueue q) { - IPAddress ipAddress; - if (IPAddress.TryParse(value.Machine, out ipAddress)) - { - return PREFIX_TCP + MsmqQueueCreator.GetFullPathWithoutPrefix(value); - } - - return PREFIX + MsmqQueueCreator.GetFullPathWithoutPrefix(value); - } - - /// - /// Gets the name of the return address from the provided value. - /// If the target includes a machine name, uses the local machine name in the returned value - /// otherwise uses the local IP address in the returned value. - /// - public static string GetReturnAddress(string value, string target) - { - return GetReturnAddress(Address.Parse(value), Address.Parse(target)); - } - - /// - /// Gets the name of the return address from the provided value. - /// If the target includes a machine name, uses the local machine name in the returned value - /// otherwise uses the local IP address in the returned value. - /// - public static string GetReturnAddress(Address value, Address target) - { - var machine = target.Machine; - - IPAddress targetIpAddress; - - //see if the target is an IP address, if so, get our own local ip address - if (IPAddress.TryParse(machine, out targetIpAddress)) - { - if (string.IsNullOrEmpty(localIp)) - { - localIp = LocalIpAddress(targetIpAddress); - } - - return PREFIX_TCP + localIp + PRIVATE + value.Queue; - } - - return PREFIX + MsmqQueueCreator.GetFullPathWithoutPrefix(value); - } - - static string LocalIpAddress(IPAddress targetIpAddress) - { - var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); - - var availableAddresses = - networkInterfaces.Where( - ni => - ni.OperationalStatus == OperationalStatus.Up && - ni.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .SelectMany(ni => ni.GetIPProperties().UnicastAddresses).ToList(); - - var firstWithMatchingFamily = - availableAddresses.FirstOrDefault(a => a.Address.AddressFamily == targetIpAddress.AddressFamily); - - if (firstWithMatchingFamily != null) - { - return firstWithMatchingFamily.Address.ToString(); - } - - var fallbackToDifferentFamily = availableAddresses.FirstOrDefault(); - - if (fallbackToDifferentFamily != null) - { - return fallbackToDifferentFamily.Address.ToString(); - } - - return "127.0.0.1"; - } - - - static Address GetIndependentAddressForQueue(MessageQueue q) - { - if (q == null) - { - return null; - } - var arr = q.FormatName.Split('\\'); var queueName = arr[arr.Length - 1]; var directPrefixIndex = arr[0].IndexOf(DIRECTPREFIX); if (directPrefixIndex >= 0) { - return new Address(queueName, arr[0].Substring(directPrefixIndex + DIRECTPREFIX.Length)); + return new MsmqAddress(queueName, arr[0].Substring(directPrefixIndex + DIRECTPREFIX.Length)); } var tcpPrefixIndex = arr[0].IndexOf(DIRECTPREFIX_TCP); if (tcpPrefixIndex >= 0) { - return new Address(queueName, arr[0].Substring(tcpPrefixIndex + DIRECTPREFIX_TCP.Length)); + return new MsmqAddress(queueName, arr[0].Substring(tcpPrefixIndex + DIRECTPREFIX_TCP.Length)); } try @@ -124,46 +37,32 @@ static Address GetIndependentAddressForQueue(MessageQueue q) // the pessimistic approach failed, try the optimistic approach arr = q.QueueName.Split('\\'); queueName = arr[arr.Length - 1]; - return new Address(queueName, q.MachineName); + return new MsmqAddress(queueName, q.MachineName); } catch { - throw new Exception("Could not translate format name to independent name: " + q.FormatName); + throw new Exception($"Could not translate format name to independent name: {q.FormatName}"); } } - /// - /// Converts an MSMQ message to a TransportMessage. - /// - public static TransportMessage Convert(Message m) + public static Dictionary ExtractHeaders(Message msmqMessage) { - var headers = DeserializeMessageHeaders(m); - - - var result = new TransportMessage(m.Id, headers) - { - Recoverable = m.Recoverable, - TimeToBeReceived = m.TimeToBeReceived, - CorrelationId = GetCorrelationId(m, headers) - }; + var headers = DeserializeMessageHeaders(msmqMessage); //note: we can drop this line when we no longer support interop btw v3 + v4 - if (m.ResponseQueue != null) + if (msmqMessage.ResponseQueue != null) { - result.Headers[Headers.ReplyToAddress] = GetIndependentAddressForQueue(m.ResponseQueue).ToString(); + headers[Headers.ReplyToAddress] = GetIndependentAddressForQueue(msmqMessage.ResponseQueue).ToString(); } - - if (Enum.IsDefined(typeof(MessageIntentEnum), m.AppSpecific)) + if (Enum.IsDefined(typeof(MessageIntentEnum), msmqMessage.AppSpecific)) { - result.MessageIntent = (MessageIntentEnum) m.AppSpecific; + headers[Headers.MessageIntent] = ((MessageIntentEnum) msmqMessage.AppSpecific).ToString(); } - m.BodyStream.Position = 0; - result.Body = new byte[m.BodyStream.Length]; - m.BodyStream.Read(result.Body, 0, result.Body.Length); + headers[Headers.CorrelationId] = GetCorrelationId(msmqMessage, headers); - return result; + return headers; } static string GetCorrelationId(Message message, Dictionary headers) @@ -182,7 +81,7 @@ static string GetCorrelationId(Message message, Dictionary heade //msmq required the id's to be in the {guid}\{incrementing number} format so we need to fake a \0 at the end that the sender added to make it compatible //The replace can be removed in v5 since only v3 messages will need this - return message.CorrelationId.Replace("\\0", ""); + return message.CorrelationId.Replace("\\0", string.Empty); } static Dictionary DeserializeMessageHeaders(Message m) @@ -209,7 +108,7 @@ static Dictionary DeserializeMessageHeaders(Message m) } } - foreach (var pair in (List)o) + foreach (var pair in (List) o) { if (pair.Key != null) { @@ -220,11 +119,7 @@ static Dictionary DeserializeMessageHeaders(Message m) return result; } - /// - /// Converts a TransportMessage to an Msmq message. - /// Doesn't set the ResponseQueue of the result. - /// - public static Message Convert(TransportMessage message) + public static Message Convert(OutgoingMessage message, List deliveryConstraints) { var result = new Message(); @@ -235,73 +130,173 @@ public static Message Convert(TransportMessage message) AssignMsmqNativeCorrelationId(message, result); + result.Recoverable = !deliveryConstraints.Any(c => c is NonDurableDelivery); - result.Recoverable = message.Recoverable; + DiscardIfNotReceivedBefore timeToBeReceived; - if (message.TimeToBeReceived < MessageQueue.InfiniteTimeout) + if (deliveryConstraints.TryGet(out timeToBeReceived) && timeToBeReceived.MaxTime < MessageQueue.InfiniteTimeout) { - result.TimeToBeReceived = message.TimeToBeReceived; + result.TimeToBeReceived = timeToBeReceived.MaxTime; } + var addCorrIdHeader = !message.Headers.ContainsKey("CorrId"); + using (var stream = new MemoryStream()) { - headerSerializer.Serialize(stream, message.Headers.Select(pair => new HeaderInfo + var headers = message.Headers.Select(pair => new HeaderInfo { Key = pair.Key, Value = pair.Value - }).ToList()); + }).ToList(); + + if (addCorrIdHeader) + { + headers.Add(new HeaderInfo + { + Key = "CorrId", + Value = result.CorrelationId + }); + } + + headerSerializer.Serialize(stream, headers); result.Extension = stream.ToArray(); } - result.AppSpecific = (int) message.MessageIntent; + var messageIntent = default(MessageIntentEnum); + + string messageIntentString; + + if (message.Headers.TryGetValue(Headers.MessageIntent, out messageIntentString)) + { + Enum.TryParse(messageIntentString, true, out messageIntent); + } + + result.AppSpecific = (int) messageIntent; + return result; } - static void AssignMsmqNativeCorrelationId(TransportMessage message, Message result) + static void AssignMsmqNativeCorrelationId(OutgoingMessage message, Message result) { - if (string.IsNullOrEmpty(message.CorrelationId)) + string correlationIdHeader; + + if (!message.Headers.TryGetValue(Headers.CorrelationId, out correlationIdHeader)) + { + return; + } + + if (string.IsNullOrEmpty(correlationIdHeader)) { return; } Guid correlationId; - if (Guid.TryParse(message.CorrelationId, out correlationId)) + if (Guid.TryParse(correlationIdHeader, out correlationId)) { - //msmq required the id's to be in the {guid}\{incrementing number} format so we need to fake a \0 at the end to make it compatible - result.CorrelationId = message.CorrelationId + "\\0"; + //msmq required the id's to be in the {guid}\{incrementing number} format so we need to fake a \0 at the end to make it compatible + result.CorrelationId = $"{correlationIdHeader}\\0"; return; } try { - if (message.CorrelationId.Contains("\\")) + if (correlationIdHeader.Contains("\\")) { - var parts = message.CorrelationId.Split('\\'); + var parts = correlationIdHeader.Split('\\'); int number; - if (parts.Count() == 2 && Guid.TryParse(parts.First(), out correlationId) && + if (parts.Length == 2 && Guid.TryParse(parts.First(), out correlationId) && int.TryParse(parts[1], out number)) { - result.CorrelationId = message.CorrelationId; + result.CorrelationId = correlationIdHeader; } } } catch (Exception ex) { - Logger.Warn("Failed to assign a native correlation id for message: " + message.Id, ex); + Logger.Warn($"Failed to assign a native correlation id for message: {message.MessageId}", ex); + } + } + + public static bool TryOpenQueue(MsmqAddress msmqAddress, out MessageQueue messageQueue) + { + messageQueue = null; + + var queuePath = msmqAddress.PathWithoutPrefix; + + Logger.Debug($"Checking if queue exists: {queuePath}."); + + if (msmqAddress.IsRemote) + { + Logger.Debug("Queue is on remote machine."); + Logger.Debug("If this does not succeed (like if the remote machine is disconnected), processing will continue."); + } + + var path = msmqAddress.PathWithoutPrefix; + try + { + if (MessageQueue.Exists(path)) + { + messageQueue = new MessageQueue(path); + + Logger.DebugFormat("Verified that the queue: [{0}] existed", queuePath); + + return true; + } + } + catch (MessageQueueException) + { + // Can happen because of an invalid queue path or trying to access a remote private queue. + // Either way, this results in a failed attempt, therefore returning false. + + return false; + } + + return false; + } + + public static bool TryCreateQueue(MsmqAddress msmqAddress, string account, bool transactional, out MessageQueue messageQueue) + { + messageQueue = null; + + var queuePath = msmqAddress.PathWithoutPrefix; + var created = false; + + try + { + messageQueue = MessageQueue.Create(queuePath, transactional); + + Logger.DebugFormat($"Created queue, path: [{queuePath}], identity: [{account}], transactional: [{transactional}]"); + + created = true; + } + catch (MessageQueueException ex) + { + var logError = !(msmqAddress.IsRemote && (ex.MessageQueueErrorCode == MessageQueueErrorCode.IllegalQueuePathName)); + + if (ex.MessageQueueErrorCode == MessageQueueErrorCode.QueueExists) + { + //Solve the race condition problem when multiple endpoints try to create same queue (e.g. error queue). + logError = false; + } + + if (logError) + { + Logger.Error($"Could not create queue {msmqAddress}. Processing will still continue.", ex); + } } + + return created; } const string DIRECTPREFIX = "DIRECT=OS:"; const string DIRECTPREFIX_TCP = "DIRECT=TCP:"; - const string PREFIX_TCP = "FormatName:" + DIRECTPREFIX_TCP; - const string PREFIX = "FormatName:" + DIRECTPREFIX; internal const string PRIVATE = "\\private$\\"; - static string localIp; + static System.Xml.Serialization.XmlSerializer headerSerializer = new System.Xml.Serialization.XmlSerializer(typeof(List)); static ILog Logger = LogManager.GetLogger(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/QueueCreator.cs b/src/NServiceBus.Core/Transports/Msmq/QueueCreator.cs new file mode 100644 index 00000000000..a60fffd9ad9 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/QueueCreator.cs @@ -0,0 +1,65 @@ +namespace NServiceBus +{ + using System.Messaging; + using System.Threading.Tasks; + using Features; + using Logging; + using Transport; + + class QueueCreator : ICreateQueues + { + public QueueCreator(MsmqSettings settings) + { + this.settings = settings; + } + + public Task CreateQueueIfNecessary(QueueBindings queueBindings, string identity) + { + foreach (var receivingAddress in queueBindings.ReceivingAddresses) + { + CreateQueueIfNecessary(receivingAddress, identity); + } + + foreach (var sendingAddress in queueBindings.SendingAddresses) + { + CreateQueueIfNecessary(sendingAddress, identity); + } + + return TaskEx.CompletedTask; + } + + void CreateQueueIfNecessary(string address, string identity) + { + if (address == null) + { + return; + } + + var msmqAddress = MsmqAddress.Parse(address); + + Logger.Debug($"Creating '{address}' if needed."); + + MessageQueue queue; + if (MsmqUtilities.TryOpenQueue(msmqAddress, out queue) || MsmqUtilities.TryCreateQueue(msmqAddress, identity, settings.UseTransactionalQueues, out queue)) + { + using (queue) + { + Logger.Debug("Setting queue permissions."); + + try + { + QueuePermissions.SetPermissionsForQueue(queue, identity); + } + catch (MessageQueueException ex) + { + Logger.Error($"Unable to set permissions for queue {queue.QueueName}", ex); + } + } + } + } + + MsmqSettings settings; + + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/QueuePermissions.cs b/src/NServiceBus.Core/Transports/Msmq/QueuePermissions.cs new file mode 100644 index 00000000000..f917874c7c0 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/QueuePermissions.cs @@ -0,0 +1,76 @@ +namespace NServiceBus.Features +{ + using System.Diagnostics; + using System.Messaging; + using System.Security; + using System.Security.Principal; + using Logging; + + static class QueuePermissions + { + public static void CheckQueue(string address) + { + var msmqAddress = MsmqAddress.Parse(address); + + MessageQueue messageQueue; + if (!MsmqUtilities.TryOpenQueue(msmqAddress, out messageQueue)) + { + Logger.Warn($"Unable to open the queue at address '{address}'. Make sure the queue exists, and the address is correct. Processing will still continue."); + return; + } + + using (messageQueue) + { + WarnIfPublicAccess(messageQueue); + } + } + + static void WarnIfPublicAccess(MessageQueue queue) + { + MessageQueueAccessRights? everyoneRights, anonymousRights; + + try + { + queue.TryGetPermissions(LocalAnonymousLogonName, out anonymousRights); + queue.TryGetPermissions(LocalEveryoneGroupName, out everyoneRights); + } + catch (SecurityException se) + { + Logger.Warn($"Unable to read permissions for queue [{queue.QueueName}]. Make sure you have administrative access on the target machine", se); + return; + } + + if (anonymousRights.HasValue && everyoneRights.HasValue) + { + var logMessage = $"Queue [{queue.QueueName}] is running with [{LocalEveryoneGroupName}] and [{LocalAnonymousLogonName}] permissions. Consider setting appropriate permissions, if required by the organization. For more information, consult the documentation."; + + if (Debugger.IsAttached) + { + Logger.Info(logMessage); + } + else + { + Logger.Warn(logMessage); + } + } + } + + public static void SetPermissionsForQueue(MessageQueue queue, string account) + { + + queue.SetPermissions(LocalAdministratorsGroupName, MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow); + queue.SetPermissions(LocalEveryoneGroupName, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); + queue.SetPermissions(LocalAnonymousLogonName, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); + + queue.SetPermissions(account, MessageQueueAccessRights.WriteMessage, AccessControlEntryType.Allow); + queue.SetPermissions(account, MessageQueueAccessRights.ReceiveMessage, AccessControlEntryType.Allow); + queue.SetPermissions(account, MessageQueueAccessRights.PeekMessage, AccessControlEntryType.Allow); + } + + static string LocalAdministratorsGroupName = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Translate(typeof(NTAccount)).ToString(); + static string LocalEveryoneGroupName = new SecurityIdentifier(WellKnownSidType.WorldSid, null).Translate(typeof(NTAccount)).ToString(); + static string LocalAnonymousLogonName = new SecurityIdentifier(WellKnownSidType.AnonymousSid, null).Translate(typeof(NTAccount)).ToString(); + + static ILog Logger = LogManager.GetLogger(typeof(QueuePermissions)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/ReceiveStrategy.cs b/src/NServiceBus.Core/Transports/Msmq/ReceiveStrategy.cs new file mode 100644 index 00000000000..b21299d55e3 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/ReceiveStrategy.cs @@ -0,0 +1,152 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Messaging; + using System.Threading; + using System.Threading.Tasks; + using Extensibility; + using Logging; + using Transport; + + abstract class ReceiveStrategy + { + public abstract Task ReceiveMessage(); + + public void Init(MessageQueue inputQueue, MessageQueue errorQueue, Func onMessage, Func> onError, CriticalError criticalError) + { + this.inputQueue = inputQueue; + this.errorQueue = errorQueue; + this.onMessage = onMessage; + this.onError = onError; + this.criticalError = criticalError; + } + + protected bool TryReceive(MessageQueueTransactionType transactionType, out Message message) + { + try + { + message = inputQueue.Receive(TimeSpan.FromMilliseconds(10), transactionType); + + return true; + } + catch (MessageQueueException ex) + { + if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) + { + //We should only get an IOTimeout exception here if another process removed the message between us peeking and now. + message = null; + return false; + } + throw; + } + } + + protected bool TryReceive(MessageQueueTransaction transaction, out Message message) + { + try + { + message = inputQueue.Receive(TimeSpan.FromMilliseconds(10), transaction); + + return true; + } + catch (MessageQueueException ex) + { + if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) + { + //We should only get an IOTimeout exception here if another process removed the message between us peeking and now. + message = null; + return false; + } + throw; + } + } + + protected bool TryExtractHeaders(Message message, out Dictionary headers) + { + try + { + headers = MsmqUtilities.ExtractHeaders(message); + return true; + } + catch (Exception ex) + { + var error = $"Message '{message.Id}' has corrupted headers"; + + Logger.Warn(error, ex); + + headers = null; + return false; + } + } + + protected void MovePoisonMessageToErrorQueue(Message message, MessageQueueTransaction transaction) + { + var error = $"Message '{message.Id}' is classfied as a poison message and will be moved to '{errorQueue.QueueName}'"; + + Logger.Error(error); + + errorQueue.Send(message, transaction); + } + + protected void MovePoisonMessageToErrorQueue(Message message, MessageQueueTransactionType transactionType) + { + var error = $"Message '{message.Id}' is classfied as a poison message and will be moved to '{errorQueue.QueueName}'"; + + Logger.Error(error); + + errorQueue.Send(message, transactionType); + } + + protected async Task TryProcessMessage(Message message, Dictionary headers, Stream bodyStream, TransportTransaction transaction) + { + using (var tokenSource = new CancellationTokenSource()) + { + var body = await ReadStream(message.BodyStream).ConfigureAwait(false); + var messageContext = new MessageContext(message.Id, headers, body, transaction, tokenSource, new ContextBag()); + + await onMessage(messageContext).ConfigureAwait(false); + + return tokenSource.Token.IsCancellationRequested; + } + } + + protected async Task HandleError(Message message, Dictionary headers, Exception exception, TransportTransaction transportTransaction, int processingAttempts) + { + try + { + var body = await ReadStream(message.BodyStream).ConfigureAwait(false); + var errorContext = new ErrorContext(exception, headers, message.Id, body, transportTransaction, processingAttempts); + + return await onError(errorContext).ConfigureAwait(false); + } + catch (Exception ex) + { + criticalError.Raise($"Failed to execute recoverability actions for message `{message.Id}`", ex); + + //best thing we can do is roll the message back if possible + return ErrorHandleResult.RetryRequired; + } + } + + static async Task ReadStream(Stream bodyStream) + { + bodyStream.Seek(0, SeekOrigin.Begin); + var length = (int) bodyStream.Length; + var body = new byte[length]; + await bodyStream.ReadAsync(body, 0, length).ConfigureAwait(false); + return body; + } + + protected bool IsQueuesTransactional => errorQueue.Transactional; + + MessageQueue inputQueue; + MessageQueue errorQueue; + Func onMessage; + Func> onError; + CriticalError criticalError; + + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNativeTransaction.cs b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNativeTransaction.cs new file mode 100644 index 00000000000..7b4e93d1065 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNativeTransaction.cs @@ -0,0 +1,112 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using System.Threading.Tasks; + using Transport; + + class ReceiveWithNativeTransaction : ReceiveStrategy + { + public ReceiveWithNativeTransaction(MsmqFailureInfoStorage failureInfoStorage) + { + this.failureInfoStorage = failureInfoStorage; + } + + public override async Task ReceiveMessage() + { + Message message = null; + + try + { + using (var msmqTransaction = new MessageQueueTransaction()) + { + msmqTransaction.Begin(); + + if (!TryReceive(msmqTransaction, out message)) + { + return; + } + + Dictionary headers; + + if (!TryExtractHeaders(message, out headers)) + { + MovePoisonMessageToErrorQueue(message, msmqTransaction); + + msmqTransaction.Commit(); + return; + } + + var shouldCommit = await ProcessMessage(msmqTransaction, message, headers).ConfigureAwait(false); + + if (shouldCommit) + { + msmqTransaction.Commit(); + } + else + { + msmqTransaction.Abort(); + } + } + } + // We'll only get here if Commit/Abort/Dispose throws which should be rare. + // Note: If that happens the attempts counter will be inconsistent since the message might be picked up again before we can register the failure in the LRU cache. + catch (Exception exception) + { + if (message == null) + { + throw; + } + + failureInfoStorage.RecordFailureInfoForMessage(message.Id, exception); + } + } + + async Task ProcessMessage(MessageQueueTransaction msmqTransaction, Message message, Dictionary headers) + { + var transportTransaction = new TransportTransaction(); + + transportTransaction.Set(msmqTransaction); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + + var shouldTryProcessMessage = true; + + if (failureInfoStorage.TryGetFailureInfoForMessage(message.Id, out failureInfo)) + { + var errorHandleResult = await HandleError(message, headers, failureInfo.Exception, transportTransaction, failureInfo.NumberOfProcessingAttempts).ConfigureAwait(false); + + shouldTryProcessMessage = errorHandleResult != ErrorHandleResult.Handled; + } + + if (shouldTryProcessMessage) + { + try + { + using (var bodyStream = message.BodyStream) + { + var shouldAbortMessageProcessing = await TryProcessMessage(message, headers, bodyStream, transportTransaction).ConfigureAwait(false); + + if (shouldAbortMessageProcessing) + { + return false; + } + } + } + catch (Exception exception) + { + failureInfoStorage.RecordFailureInfoForMessage(message.Id, exception); + + return false; + } + } + + failureInfoStorage.ClearFailureInfoForMessage(message.Id); + + return true; + } + + MsmqFailureInfoStorage failureInfoStorage; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNoTransaction.cs b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNoTransaction.cs new file mode 100644 index 00000000000..c171052d2a2 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithNoTransaction.cs @@ -0,0 +1,45 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using System.Threading.Tasks; + using Transport; + + class ReceiveWithNoTransaction : ReceiveStrategy + { + public override async Task ReceiveMessage() + { + Message message; + + if (!TryReceive(MessageQueueTransactionType.None, out message)) + { + return; + } + + Dictionary headers; + + if (!TryExtractHeaders(message, out headers)) + { + MovePoisonMessageToErrorQueue(message, IsQueuesTransactional ? MessageQueueTransactionType.Single : MessageQueueTransactionType.None); + return; + } + + var transportTransaction = new TransportTransaction(); + + using (var bodyStream = message.BodyStream) + { + try + { + await TryProcessMessage(message, headers, bodyStream, transportTransaction).ConfigureAwait(false); + } + catch (Exception exception) + { + message.BodyStream.Position = 0; + + await HandleError(message, headers, exception, transportTransaction, 1).ConfigureAwait(false); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/ReceiveWithTransactionScope.cs b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithTransactionScope.cs new file mode 100644 index 00000000000..483625a1ea3 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/ReceiveWithTransactionScope.cs @@ -0,0 +1,106 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using System.Threading.Tasks; + using System.Transactions; + using Transport; + + class ReceiveWithTransactionScope : ReceiveStrategy + { + public ReceiveWithTransactionScope(TransactionOptions transactionOptions, MsmqFailureInfoStorage failureInfoStorage) + { + this.transactionOptions = transactionOptions; + this.failureInfoStorage = failureInfoStorage; + } + + public override async Task ReceiveMessage() + { + Message message = null; + try + { + using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, transactionOptions, TransactionScopeAsyncFlowOption.Enabled)) + { + if (!TryReceive(MessageQueueTransactionType.Automatic, out message)) + { + return; + } + + Dictionary headers; + + if (!TryExtractHeaders(message, out headers)) + { + MovePoisonMessageToErrorQueue(message, MessageQueueTransactionType.Automatic); + + scope.Complete(); + return; + } + + var shouldCommit = await ProcessMessage(message, headers).ConfigureAwait(false); + + if (!shouldCommit) + { + return; + } + + scope.Complete(); + } + } + // We'll only get here if Complete/Dispose throws which should be rare. + // Note: If that happens the attempts counter will be inconsistent since the message might be picked up again before we can register the failure in the LRU cache. + catch (Exception exception) + { + if (message == null) + { + throw; + } + + failureInfoStorage.RecordFailureInfoForMessage(message.Id, exception); + } + } + + async Task ProcessMessage(Message message, Dictionary headers) + { + var transportTransaction = new TransportTransaction(); + transportTransaction.Set(Transaction.Current); + + MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo; + + if (failureInfoStorage.TryGetFailureInfoForMessage(message.Id, out failureInfo)) + { + var errorHandleResult = await HandleError(message, headers, failureInfo.Exception, transportTransaction, failureInfo.NumberOfProcessingAttempts).ConfigureAwait(false); + + if (errorHandleResult == ErrorHandleResult.Handled) + { + failureInfoStorage.ClearFailureInfoForMessage(message.Id); + return true; + } + } + + try + { + using (var bodyStream = message.BodyStream) + { + var shouldAbortMessageProcessing = await TryProcessMessage(message, headers, bodyStream, transportTransaction).ConfigureAwait(false); + + if (shouldAbortMessageProcessing) + { + return false; + } + } + + failureInfoStorage.ClearFailureInfoForMessage(message.Id); + return true; + } + catch (Exception exception) + { + failureInfoStorage.RecordFailureInfoForMessage(message.Id, exception); + return false; + } + } + + TransactionOptions transactionOptions; + MsmqFailureInfoStorage failureInfoStorage; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/TimeToBeRceivedOverrideChecker.cs b/src/NServiceBus.Core/Transports/Msmq/TimeToBeRceivedOverrideChecker.cs deleted file mode 100644 index 3d9d40cd125..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/TimeToBeRceivedOverrideChecker.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using System; - - class TimeToBeRceivedOverrideChecker - { - public static void Check(bool usingMsmq, bool isTransactional, bool outBoxRunning, bool auditTTBROverridden, bool forwardTTBROverridden) - { - if (!usingMsmq) - { - return; - } - - if (!isTransactional) - { - return; - } - - if (outBoxRunning) - { - return; - } - - if (auditTTBROverridden) - { - throw new Exception("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ."); - } - - if (forwardTTBROverridden) - { - throw new Exception("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ."); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideCheck.cs b/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideCheck.cs deleted file mode 100644 index 613b91f1bbf..00000000000 --- a/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideCheck.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Transports.Msmq -{ - using NServiceBus.Config; - using System; - - class TimeToBeReceivedOverrideCheck : IWantToRunWhenConfigurationIsComplete - { - public void Run(Configure config) - { - var usingMsmq = config.Settings.Get() is MsmqTransport; - var isTransactional = config.Settings.Get("Transactions.Enabled"); - var outBoxRunning = config.Settings.GetOrDefault("NServiceBus.Features.Outbox"); - - var messageAuditingConfig = config.Settings.GetConfigSection(); - var auditTTBROverridden = messageAuditingConfig != null && messageAuditingConfig.OverrideTimeToBeReceived > TimeSpan.Zero; - - var unicastBusConfig = config.Settings.GetConfigSection(); - var forwardTTBROverridden = unicastBusConfig != null && unicastBusConfig.TimeToBeReceivedOnForwardedMessages > TimeSpan.Zero; - - TimeToBeRceivedOverrideChecker.Check(usingMsmq, isTransactional, outBoxRunning, auditTTBROverridden, forwardTTBROverridden); - } - } -} diff --git a/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideChecker.cs b/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideChecker.cs new file mode 100644 index 00000000000..1c10850d405 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Msmq/TimeToBeReceivedOverrideChecker.cs @@ -0,0 +1,36 @@ +namespace NServiceBus +{ + using Transport; + + class TimeToBeReceivedOverrideChecker + { + public static StartupCheckResult Check(bool usingMsmq, bool isTransactional, bool outBoxRunning, bool auditTTBROverridden, bool forwardTTBROverridden) + { + if (!usingMsmq) + { + return StartupCheckResult.Success; + } + + if (!isTransactional) + { + return StartupCheckResult.Success; + } + + if (outBoxRunning) + { + return StartupCheckResult.Success; + } + + if (auditTTBROverridden) + { + return StartupCheckResult.Failed("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ."); + } + + if (forwardTTBROverridden) + { + return StartupCheckResult.Failed("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ."); + } + return StartupCheckResult.Success; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/MulticastTransportOperation.cs b/src/NServiceBus.Core/Transports/MulticastTransportOperation.cs new file mode 100644 index 00000000000..d2037a9b5e9 --- /dev/null +++ b/src/NServiceBus.Core/Transports/MulticastTransportOperation.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Collections.Generic; + using DeliveryConstraints; + + /// + /// Represents a transport operation which should be delivered to multiple receivers. + /// + public class MulticastTransportOperation : IOutgoingTransportOperation + { + /// + /// Creates a new instance. + /// + // ReSharper disable once ParameterTypeCanBeEnumerable.Local + public MulticastTransportOperation(OutgoingMessage message, Type messageType, DispatchConsistency requiredDispatchConsistency = DispatchConsistency.Default, List deliveryConstraints = null) + { + Message = message; + MessageType = messageType; + DeliveryConstraints = deliveryConstraints ?? DeliveryConstraint.EmptyConstraints; + RequiredDispatchConsistency = requiredDispatchConsistency; + } + + /// + /// Defines the message type which needs to be multicasted. + /// + public Type MessageType { get; } + + /// + /// The delivery constraints that must be honored by the transport. + /// + /// The delivery constraints should only ever be read. When there are no delivery constraints a cached empty constraints list is returned. + public List DeliveryConstraints { get; } + + /// + /// The message to be sent over the transport. + /// + public OutgoingMessage Message { get; } + + /// + /// The dispatch consistency the must be honored by the transport. + /// + public DispatchConsistency RequiredDispatchConsistency { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/OutboundRoutingPolicy.cs b/src/NServiceBus.Core/Transports/OutboundRoutingPolicy.cs new file mode 100644 index 00000000000..9135206ee03 --- /dev/null +++ b/src/NServiceBus.Core/Transports/OutboundRoutingPolicy.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Transport +{ + /// + /// Defines the policy for outbound routing. + /// + public class OutboundRoutingPolicy + { + /// + /// Creates new policy object. + /// + /// Policy for sends. + /// Policy for publishes. + /// Policy for replies. + public OutboundRoutingPolicy(OutboundRoutingType sends, OutboundRoutingType publishes, OutboundRoutingType replies) + { + Sends = sends; + Publishes = publishes; + Replies = replies; + } + + /// + /// Gets the policy for sends. + /// + public OutboundRoutingType Sends { get; } + + /// + /// Gets the policy for publishes. + /// + public OutboundRoutingType Publishes { get; } + + /// + /// Gets the policy for replies. + /// + public OutboundRoutingType Replies { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/OutboundRoutingType.cs b/src/NServiceBus.Core/Transports/OutboundRoutingType.cs new file mode 100644 index 00000000000..98f000c36a7 --- /dev/null +++ b/src/NServiceBus.Core/Transports/OutboundRoutingType.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Transport +{ + /// + /// The type of routing from the perspective of a transport. + /// + public enum OutboundRoutingType + { + /// + /// Unicast. Routing is performed by the core and one send operation might require multiple calls to + /// . + /// + Unicast, + + /// + /// Multicast. Routing is performed by the transport. + /// + Multicast + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/OutgoingMessage.cs b/src/NServiceBus.Core/Transports/OutgoingMessage.cs new file mode 100644 index 00000000000..46be532212f --- /dev/null +++ b/src/NServiceBus.Core/Transports/OutgoingMessage.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + + /// + /// The message going out to the transport. + /// + public class OutgoingMessage + { + /// + /// Initializes a new instance of . + /// + /// The message id to use. + /// The headers associated with this message. + /// The body of the message. + public OutgoingMessage(string messageId, Dictionary headers, byte[] body) + { + MessageId = messageId; + Headers = headers; + Body = body; + } + + /// + /// The body to be sent. + /// + public byte[] Body { get; } + + + /// + /// The id of the message. + /// + public string MessageId { get; } + + /// + /// The headers for the message. + /// + public Dictionary Headers { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/PushRuntimeSettings.cs b/src/NServiceBus.Core/Transports/PushRuntimeSettings.cs new file mode 100644 index 00000000000..f5c6be0ddf5 --- /dev/null +++ b/src/NServiceBus.Core/Transports/PushRuntimeSettings.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.Transport +{ + using System; + + /// + /// Controls how the message pump should behave. + /// + public class PushRuntimeSettings + { + /// + /// Constructs the settings. NServiceBus will pick a suitable default for `MaxConcurrency`. + /// + public PushRuntimeSettings() + { + MaxConcurrency = Math.Max(2, Environment.ProcessorCount); + } + + /// + /// Constructs the settings. + /// + /// The maximum concurrency to allow. + public PushRuntimeSettings(int maxConcurrency) + { + Guard.AgainstNegativeAndZero(nameof(maxConcurrency), maxConcurrency); + + MaxConcurrency = maxConcurrency; + } + + /// + /// The maximum number of messages that should be in flight at any given time. + /// + public int MaxConcurrency { get; private set; } + + /// + /// Use default settings. + /// + public static PushRuntimeSettings Default => new PushRuntimeSettings(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/PushSettings.cs b/src/NServiceBus.Core/Transports/PushSettings.cs new file mode 100644 index 00000000000..189dba34157 --- /dev/null +++ b/src/NServiceBus.Core/Transports/PushSettings.cs @@ -0,0 +1,47 @@ +namespace NServiceBus.Transport +{ + /// + /// Contains information necessary to set up a message pump for receiving messages. + /// + public class PushSettings + { + /// + /// Creates an instance of . + /// + /// Input queue name. + /// Error queue name. + /// true to purge at startup. + /// The transaction mode required for receive operations. + public PushSettings(string inputQueue, string errorQueue, bool purgeOnStartup, TransportTransactionMode requiredTransactionMode) + { + Guard.AgainstNullAndEmpty(nameof(inputQueue), inputQueue); + Guard.AgainstNullAndEmpty(nameof(errorQueue), errorQueue); + Guard.AgainstNull(nameof(requiredTransactionMode), requiredTransactionMode); + + PurgeOnStartup = purgeOnStartup; + RequiredTransactionMode = requiredTransactionMode; + InputQueue = inputQueue; + ErrorQueue = errorQueue; + } + + /// + /// The native queue to consume messages from. + /// + public string InputQueue { get; private set; } + + /// + /// The native queue where to send corrupted messages to. + /// + public string ErrorQueue { get; private set; } + + /// + /// Instructs the message pump to purge the `InputQueue` before starting to push messages from it. + /// + public bool PurgeOnStartup { get; private set; } + + /// + /// The transaction mode required for receive operations. + /// + public TransportTransactionMode RequiredTransactionMode { get; private set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/QueueBindings.cs b/src/NServiceBus.Core/Transports/QueueBindings.cs new file mode 100644 index 00000000000..9e80bd57b3d --- /dev/null +++ b/src/NServiceBus.Core/Transports/QueueBindings.cs @@ -0,0 +1,41 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + + /// + /// Contains information about queues this endpoint is using. + /// + public class QueueBindings + { + /// + /// Returns the collection of all transport addresses of queues this endpoint is receiving from. + /// + public IReadOnlyCollection ReceivingAddresses => receiveAddresses; + + /// + /// Returns the collection of all transport addresses of queues this endpoint is sending to. + /// + public IReadOnlyCollection SendingAddresses => sendingAddresses; + + /// + /// Declares that this endpoint will be using queue with address for receiving. + /// + /// The address of the queue. + public void BindReceiving(string address) + { + receiveAddresses.Add(address); + } + + /// + /// Declares that this endpoint will be using queue with address for sending. + /// + /// The address of the queue. + public void BindSending(string transportAddress) + { + sendingAddresses.Add(transportAddress); + } + + List receiveAddresses = new List(); + List sendingAddresses = new List(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ReceivePipelineCompleted.cs b/src/NServiceBus.Core/Transports/ReceivePipelineCompleted.cs new file mode 100644 index 00000000000..36216d96099 --- /dev/null +++ b/src/NServiceBus.Core/Transports/ReceivePipelineCompleted.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using System; + using Transport; + + class ReceivePipelineCompleted + { + public IncomingMessage ProcessedMessage { get; } + public DateTime StartedAt { get; } + public DateTime CompletedAt { get; } + + public ReceivePipelineCompleted(IncomingMessage processedMessage, DateTime startedAt, DateTime completedAt) + { + ProcessedMessage = processedMessage; + StartedAt = startedAt; + CompletedAt = completedAt; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/ReceiveSettingsExtensions.cs b/src/NServiceBus.Core/Transports/ReceiveSettingsExtensions.cs new file mode 100644 index 00000000000..4e471294cc9 --- /dev/null +++ b/src/NServiceBus.Core/Transports/ReceiveSettingsExtensions.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + /// + /// Configuration extensions for receive settings. + /// + public static class ReceiveSettingsExtensions + { + /// + ///Makes the endpoint instance uniquely addressable when running multiple instances by adding an instance-specific queue. + /// + /// The instance to apply the settings to. + /// The value to append to the endpoint name to create an instance-specific queue. + public static void MakeInstanceUniquelyAddressable(this EndpointConfiguration config, string discriminator) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNullAndEmpty(nameof(discriminator), discriminator); + + config.Settings.Set("EndpointInstanceDiscriminator", discriminator); + } + + /// + /// Overrides the base name of the input queue. The actual input queue name consists of this base name, instance ID and subqueue qualifier. + /// + /// The instance to apply the settings to. + /// The base name of the input queue. + public static void OverrideLocalAddress(this EndpointConfiguration config, string baseInputQueueName) + { + Guard.AgainstNullAndEmpty(nameof(baseInputQueueName), baseInputQueueName); + config.Settings.SetDefault("BaseInputQueueName", baseInputQueueName); + } + } +} diff --git a/src/NServiceBus.Core/Transports/Receiving.cs b/src/NServiceBus.Core/Transports/Receiving.cs new file mode 100644 index 00000000000..7698edb98c9 --- /dev/null +++ b/src/NServiceBus.Core/Transports/Receiving.cs @@ -0,0 +1,83 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Features; + using Routing; + using Transport; + + class Receiving : Feature + { + internal Receiving() + { + EnableByDefault(); + Prerequisite(c => !c.Settings.GetOrDefault("Endpoint.SendOnly"), "Endpoint is configured as send-only"); + Defaults(s => + { + var transportInfrastructure = s.Get(); + var discriminator = s.GetOrDefault("EndpointInstanceDiscriminator"); + var baseQueueName = s.GetOrDefault("BaseInputQueueName") ?? s.EndpointName(); + + var mainInstance = transportInfrastructure.BindToLocalEndpoint(new EndpointInstance(s.EndpointName())); + + var mainLogicalAddress = LogicalAddress.CreateLocalAddress(baseQueueName, mainInstance.Properties); + s.SetDefault(mainLogicalAddress); + + var mainAddress = transportInfrastructure.ToTransportAddress(mainLogicalAddress); + s.SetDefault("NServiceBus.SharedQueue", mainAddress); + + if (discriminator != null) + { + var instanceSpecificAddress = transportInfrastructure.ToTransportAddress(mainLogicalAddress.CreateIndividualizedAddress(discriminator)); + s.SetDefault("NServiceBus.EndpointSpecificQueue", instanceSpecificAddress); + } + }); + } + + /// + /// . + /// + protected internal override void Setup(FeatureConfigurationContext context) + { + var inboundTransport = context.Settings.Get(); + + context.Settings.Get().BindReceiving(context.Settings.LocalAddress()); + + var instanceSpecificQueue = context.Settings.InstanceSpecificQueue(); + if (instanceSpecificQueue != null) + { + context.Settings.Get().BindReceiving(instanceSpecificQueue); + } + + var lazyReceiveConfigResult = new Lazy(() => inboundTransport.Configure(context.Settings)); + context.Container.ConfigureComponent(b => lazyReceiveConfigResult.Value.MessagePumpFactory(), DependencyLifecycle.InstancePerCall); + context.Container.ConfigureComponent(b => lazyReceiveConfigResult.Value.QueueCreatorFactory(), DependencyLifecycle.SingleInstance); + + context.RegisterStartupTask(new PrepareForReceiving(lazyReceiveConfigResult)); + } + + class PrepareForReceiving : FeatureStartupTask + { + public PrepareForReceiving(Lazy lazy) + { + this.lazy = lazy; + } + + protected override async Task OnStart(IMessageSession session) + { + var result = await lazy.Value.PreStartupCheck().ConfigureAwait(false); + if (!result.Succeeded) + { + throw new Exception($"Pre start-up check failed: {result.ErrorMessage}"); + } + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + readonly Lazy lazy; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/Sending.cs b/src/NServiceBus.Core/Transports/Sending.cs new file mode 100644 index 00000000000..66fad7e99dd --- /dev/null +++ b/src/NServiceBus.Core/Transports/Sending.cs @@ -0,0 +1,53 @@ +namespace NServiceBus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Features; + using Transport; + + class Sending : Feature + { + public Sending() + { + EnableByDefault(); + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + var transport = context.Settings.Get(); + var lazySendingConfigResult = new Lazy(() => transport.Configure(context.Settings), LazyThreadSafetyMode.ExecutionAndPublication); + context.Container.ConfigureComponent(c => + { + var dispatcher = lazySendingConfigResult.Value.DispatcherFactory(); + return dispatcher; + }, DependencyLifecycle.SingleInstance); + + context.RegisterStartupTask(new PrepareForSending(lazySendingConfigResult)); + } + + class PrepareForSending : FeatureStartupTask + { + public PrepareForSending(Lazy lazy) + { + this.lazy = lazy; + } + + protected override async Task OnStart(IMessageSession session) + { + var result = await lazy.Value.PreStartupCheck().ConfigureAwait(false); + if (!result.Succeeded) + { + throw new Exception("Pre start-up check failed: " + result.ErrorMessage); + } + } + + protected override Task OnStop(IMessageSession session) + { + return TaskEx.CompletedTask; + } + + readonly Lazy lazy; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/StartupCheckResult.cs b/src/NServiceBus.Core/Transports/StartupCheckResult.cs new file mode 100644 index 00000000000..a0d0ebbfb17 --- /dev/null +++ b/src/NServiceBus.Core/Transports/StartupCheckResult.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.Transport +{ + /// + /// Represents a result of a pre-startup check. + /// + public class StartupCheckResult + { + StartupCheckResult(bool succeeded, string errorMessage) + { + Succeeded = succeeded; + ErrorMessage = errorMessage; + } + + /// + /// Returns weather the result was a success. + /// + public bool Succeeded { get; } + + /// + /// Returns the error message in case of a failure. + /// + public string ErrorMessage { get; } + + /// + /// Failure. + /// + /// Mandatory error message. + public static StartupCheckResult Failed(string errorMessage) + { + Guard.AgainstNull(nameof(errorMessage), errorMessage); + return new StartupCheckResult(false, errorMessage); + } + + /// + /// Success. + /// + public static readonly StartupCheckResult Success = new StartupCheckResult(true, null); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransactionModeSettingsExtensions.cs b/src/NServiceBus.Core/Transports/TransactionModeSettingsExtensions.cs new file mode 100644 index 00000000000..7634a74a1ba --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransactionModeSettingsExtensions.cs @@ -0,0 +1,35 @@ +namespace NServiceBus.ConsistencyGuarantees +{ + using System; + using Settings; + using Transport; + + /// + /// Extension methods to provide access to various consistency related convenience methods. + /// + public static class TransactionModeSettingsExtensions + { + /// + /// Returns the transactions required by the transport. + /// + public static TransportTransactionMode GetRequiredTransactionModeForReceives(this ReadOnlySettings settings) + { + var transportTransactionSupport = settings.Get().TransactionMode; + + TransportTransactionMode requestedTransportTransactionMode; + + //if user haven't asked for a explicit level use what the transport supports + if (!settings.TryGet(out requestedTransportTransactionMode)) + { + return transportTransactionSupport; + } + + if (requestedTransportTransactionMode > transportTransactionSupport) + { + throw new Exception($"Requested transaction mode `{requestedTransportTransactionMode}` can't be satisfied since the transport only supports `{transportTransactionSupport}`"); + } + + return requestedTransportTransactionMode; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportDefinition.cs b/src/NServiceBus.Core/Transports/TransportDefinition.cs index f052f00c455..a4b2a27baca 100644 --- a/src/NServiceBus.Core/Transports/TransportDefinition.cs +++ b/src/NServiceBus.Core/Transports/TransportDefinition.cs @@ -1,36 +1,31 @@ -namespace NServiceBus.Transports +namespace NServiceBus.Transport { + using Settings; + /// - /// Defines a transport that can be used by NServiceBus + /// Defines a transport. /// - public abstract class TransportDefinition + public abstract partial class TransportDefinition { /// - /// Indicates that the transport is capable of supporting the publish and subscribe pattern natively - /// - public bool HasNativePubSubSupport { get; protected set; } - - /// - /// Indicates that the transport has a central store for subscriptions - /// - public bool HasSupportForCentralizedPubSub { get; protected set; } - - /// - /// Indicates that the transport has support for distributed transactions + /// Gets an example connection string to use when reporting lack of configured connection string to the user. /// - public bool? HasSupportForDistributedTransactions { get; protected set; } + public abstract string ExampleConnectionStringForErrorMessage { get; } /// - /// True if the transport + /// Used by implementations to control if a connection string is necessary. /// - public bool RequireOutboxConsent { get; set; } + public virtual bool RequiresConnectionString => true; /// - /// Gives implementations access to the instance at configuration time. + /// Initializes all the factories and supported features for the transport. This method is called right before all features + /// are activated and the settings will be locked down. This means you can use the SettingsHolder both for providing + /// default capabilities as well as for initializing the transport's configuration based on those settings (the user cannot + /// provide information anymore at this stage). /// - protected internal virtual void Configure(BusConfiguration config) - { - - } + /// An instance of the current settings. + /// The connection string. + /// The supported factories. + public abstract TransportInfrastructure Initialize(SettingsHolder settings, string connectionString); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportInfrastructure.cs b/src/NServiceBus.Core/Transports/TransportInfrastructure.cs new file mode 100644 index 00000000000..a728b115dd3 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportInfrastructure.cs @@ -0,0 +1,86 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Routing; + + /// + /// Transport infrastructure definitions. + /// + public abstract class TransportInfrastructure + { + /// + /// Returns the list of supported delivery constraints for this transport. + /// + public abstract IEnumerable DeliveryConstraints { get; } + + /// + /// Gets the highest supported transaction mode for the this transport. + /// + public abstract TransportTransactionMode TransactionMode { get; } + + /// + /// Returns the outbound routing policy selected for the transport. + /// + public abstract OutboundRoutingPolicy OutboundRoutingPolicy { get; } + + /// + /// True if the transport. + /// + public bool RequireOutboxConsent { get; protected set; } + + /// + /// Gets the factories to receive message. + /// + public abstract TransportReceiveInfrastructure ConfigureReceiveInfrastructure(); + + /// + /// Gets the factories to send message. + /// + public abstract TransportSendInfrastructure ConfigureSendInfrastructure(); + + /// + /// Gets the factory to manage subscriptions. + /// + public abstract TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure(); + + /// + /// Returns the discriminator for this endpoint instance. + /// + public abstract EndpointInstance BindToLocalEndpoint(EndpointInstance instance); + + /// + /// Converts a given logical address to the transport address. + /// + /// The logical address. + /// The transport address. + public abstract string ToTransportAddress(LogicalAddress logicalAddress); + + /// + /// Returns the canonical for of the given transport address so various transport addresses can be effectively compared and + /// de-duplicated. + /// + /// A transport address. + public virtual string MakeCanonicalForm(string transportAddress) + { + return transportAddress; + } + + /// + /// Performs any action required to warm up the transport infrastructure before starting the endpoint. + /// + public virtual Task Start() + { + return TaskEx.CompletedTask; + } + + /// + /// Performs any action required to cool down the transport infrastructure when the endpoint is stopping. + /// + public virtual Task Stop() + { + return TaskEx.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportOperation.cs b/src/NServiceBus.Core/Transports/TransportOperation.cs new file mode 100644 index 00000000000..9e1593b76eb --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportOperation.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + using DeliveryConstraints; + using Routing; + + /// + /// Defines the transport operations including the message and information how to send it. + /// + public class TransportOperation + { + /// + /// Creates a new transport operation. + /// + /// The message to dispatch. + /// The address to use when routing this message. + /// The required consistency level for the dispatch operation. + /// The delivery constraints that must be honored by the transport. + public TransportOperation(OutgoingMessage message, AddressTag addressTag, DispatchConsistency requiredDispatchConsistency = DispatchConsistency.Default, List deliveryConstraints = null) + { + Message = message; + AddressTag = addressTag; + RequiredDispatchConsistency = requiredDispatchConsistency; + DeliveryConstraints = deliveryConstraints ?? DeliveryConstraint.EmptyConstraints; + } + + /// + /// Gets the message. + /// + public OutgoingMessage Message { get; } + + /// + /// The strategy to use when routing this message. + /// + public AddressTag AddressTag { get; } + + /// + /// The delivery constraints that must be honored by the transport. + /// + /// The delivery constraints should only ever be read. When there are no delivery constraints you'll get a cached empty constraints list. + public List DeliveryConstraints { get; } + + /// + /// The dispatch consistency the must be honored by the transport. + /// + public DispatchConsistency RequiredDispatchConsistency { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportOperations.cs b/src/NServiceBus.Core/Transports/TransportOperations.cs new file mode 100644 index 00000000000..7aa26b16e14 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportOperations.cs @@ -0,0 +1,61 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Collections.Generic; + using Routing; + + /// + /// Represents a set of transport operations. + /// + public class TransportOperations + { + /// + /// Creates a new set of dispatchable transport operations. + /// + public TransportOperations(params TransportOperation[] transportOperations) + { + var multicastOperations = new List(transportOperations.Length); + var unicastOperations = new List(transportOperations.Length); + + foreach (var transportOperation in transportOperations) + { + // ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull + if (transportOperation.AddressTag is MulticastAddressTag) + { + multicastOperations.Add(new MulticastTransportOperation( + transportOperation.Message, + ((MulticastAddressTag) transportOperation.AddressTag).MessageType, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints)); + } + else if (transportOperation.AddressTag is UnicastAddressTag) + { + unicastOperations.Add(new UnicastTransportOperation( + transportOperation.Message, + ((UnicastAddressTag) transportOperation.AddressTag).Destination, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints)); + } + else + { + throw new ArgumentException( + $"Transport operations contain an unsupported type of {typeof(AddressTag).Name}: {transportOperation.AddressTag.GetType().Name}. Supported types are {typeof(UnicastAddressTag).Name} and {typeof(MulticastAddressTag).Name}", + nameof(transportOperations)); + } + } + + MulticastTransportOperations = multicastOperations; + UnicastTransportOperations = unicastOperations; + } + + /// + /// A list of multicast transport operations. + /// + public List MulticastTransportOperations { get; } + + /// + /// A list of unicast transport operations. + /// + public List UnicastTransportOperations { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportReceiveInfrastructure.cs b/src/NServiceBus.Core/Transports/TransportReceiveInfrastructure.cs new file mode 100644 index 00000000000..1530d5aa450 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportReceiveInfrastructure.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents the infrastructure of the transport used for receiving. + /// + public class TransportReceiveInfrastructure + { + /// + /// Creates new result. + /// + public TransportReceiveInfrastructure( + Func messagePumpFactory, + Func queueCreatorFactory, + Func> preStartupCheck) + { + Guard.AgainstNull(nameof(messagePumpFactory), messagePumpFactory); + Guard.AgainstNull(nameof(queueCreatorFactory), queueCreatorFactory); + Guard.AgainstNull(nameof(preStartupCheck), preStartupCheck); + + MessagePumpFactory = messagePumpFactory; + QueueCreatorFactory = queueCreatorFactory; + PreStartupCheck = preStartupCheck; + } + + /// + /// Factory for creating the message pump. + /// + public Func MessagePumpFactory { get; } + + /// + /// Factory for the queue creator. + /// + public Func QueueCreatorFactory { get; } + + internal Func> PreStartupCheck { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportReceiver.cs b/src/NServiceBus.Core/Transports/TransportReceiver.cs new file mode 100644 index 00000000000..41c36b0d4b2 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportReceiver.cs @@ -0,0 +1,73 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using Logging; + using Transport; + + class TransportReceiver + { + public TransportReceiver( + string id, + IPushMessages pushMessages, + PushSettings pushSettings, + PushRuntimeSettings pushRuntimeSettings, + IPipelineExecutor pipelineExecutor, + RecoverabilityExecutor recoverabilityExecutor, + CriticalError criticalError) + { + this.criticalError = criticalError; + Id = id; + this.pushRuntimeSettings = pushRuntimeSettings; + this.pipelineExecutor = pipelineExecutor; + this.recoverabilityExecutor = recoverabilityExecutor; + this.pushSettings = pushSettings; + + receiver = pushMessages; + } + + public string Id { get; } + + public Task Init() + { + return receiver.Init(c => pipelineExecutor.Invoke(c), c => recoverabilityExecutor.Invoke(c), criticalError, pushSettings); + } + + public void Start() + { + if (isStarted) + { + throw new InvalidOperationException("The transport is already started"); + } + + Logger.DebugFormat("Receiver {0} is starting, listening to queue {1}.", Id, pushSettings.InputQueue); + + receiver.Start(pushRuntimeSettings); + + isStarted = true; + } + + public async Task Stop() + { + if (!isStarted) + { + return; + } + + await receiver.Stop().ConfigureAwait(false); + + isStarted = false; + } + + readonly CriticalError criticalError; + + bool isStarted; + PushRuntimeSettings pushRuntimeSettings; + IPipelineExecutor pipelineExecutor; + RecoverabilityExecutor recoverabilityExecutor; + PushSettings pushSettings; + IPushMessages receiver; + + static ILog Logger = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportSendInfrastructure.cs b/src/NServiceBus.Core/Transports/TransportSendInfrastructure.cs new file mode 100644 index 00000000000..1a6262bfcc4 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportSendInfrastructure.cs @@ -0,0 +1,29 @@ +namespace NServiceBus.Transport +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents the result for configuring the transport for sending. + /// + public class TransportSendInfrastructure + { + /// + /// Creates new result object. + /// + public TransportSendInfrastructure(Func dispatcherFactory, + Func> preStartupCheck) + { + Guard.AgainstNull(nameof(dispatcherFactory), dispatcherFactory); + Guard.AgainstNull(nameof(preStartupCheck), preStartupCheck); + DispatcherFactory = dispatcherFactory; + PreStartupCheck = preStartupCheck; + } + + /// + /// Factory to create the dispatcher. + /// + public Func DispatcherFactory { get; } + internal Func> PreStartupCheck { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportSubscriptionInfrastructure.cs b/src/NServiceBus.Core/Transports/TransportSubscriptionInfrastructure.cs new file mode 100644 index 00000000000..5e5651daa86 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportSubscriptionInfrastructure.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.Transport +{ + using System; + + /// + /// Represents the result for configuring the transport for subscribing. + /// + public class TransportSubscriptionInfrastructure + { + /// + /// Creates new result object. + /// + public TransportSubscriptionInfrastructure(Func subscriptionManagerFactory) + { + Guard.AgainstNull(nameof(subscriptionManagerFactory), subscriptionManagerFactory); + SubscriptionManagerFactory = subscriptionManagerFactory; + } + + internal Func SubscriptionManagerFactory { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportTransaction.cs b/src/NServiceBus.Core/Transports/TransportTransaction.cs new file mode 100644 index 00000000000..13a42ce6a45 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportTransaction.cs @@ -0,0 +1,17 @@ +namespace NServiceBus.Transport +{ + using Extensibility; + + /// + /// Represents a transaction used to receive the message from the queueing infrastructure. + /// + public sealed class TransportTransaction : ContextBag + { + /// + /// Create an instance of . + /// + public TransportTransaction() : base(null) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/TransportTransactionMode.cs b/src/NServiceBus.Core/Transports/TransportTransactionMode.cs new file mode 100644 index 00000000000..cfe43ebe295 --- /dev/null +++ b/src/NServiceBus.Core/Transports/TransportTransactionMode.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + /// + /// The different transaction levels that can be supported by a transport. + /// + public enum TransportTransactionMode + { + /// + /// No transactions used. This means that received messages will not be roll the message back to the queue if a failure + /// occurs. + /// This means that the message is lost. + /// + None = 0, + + /// + /// The receive operation will be transactional and the message will be rolled back to the queue in case of failure. + /// Outgoing queueing operations will not be enlisted in the ongoing receive transaction and therefor NOT roll back should + /// a failure occur. + /// + ReceiveOnly = 1, + + /// + /// In this mode all outgoing operations will be atomic with the current receive operations. + /// + SendsAtomicWithReceive = 2, + + /// + /// The transport enlists its receive operation in a transaction scope allowing other resource managers to participate. + /// + TransactionScope = 3 + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Transports/UnicastTransportOperation.cs b/src/NServiceBus.Core/Transports/UnicastTransportOperation.cs new file mode 100644 index 00000000000..08dbadba3b5 --- /dev/null +++ b/src/NServiceBus.Core/Transports/UnicastTransportOperation.cs @@ -0,0 +1,42 @@ +namespace NServiceBus.Transport +{ + using System.Collections.Generic; + using DeliveryConstraints; + + /// + /// Represents a transport operation which should be delivered to a single receiver. + /// + public class UnicastTransportOperation : IOutgoingTransportOperation + { + /// + /// Creates a new instance. + /// + public UnicastTransportOperation(OutgoingMessage message, string destination, DispatchConsistency requiredDispatchConsistency = DispatchConsistency.Default, List deliveryConstraints = null) + { + Message = message; + Destination = destination; + DeliveryConstraints = deliveryConstraints ?? DeliveryConstraint.EmptyConstraints; + RequiredDispatchConsistency = requiredDispatchConsistency; + } + + /// + /// Defines the destination address of the receiver. + /// + public string Destination { get; } + + /// + /// The message to be sent over the transport. + /// + public OutgoingMessage Message { get; } + + /// + /// The delivery constraints that must be honored by the transport. + /// + public List DeliveryConstraints { get; } + + /// + /// The dispatch consistency the must be honored by the transport. + /// + public DispatchConsistency RequiredDispatchConsistency { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/BackwardCompatibility/MutateMessageContentTypeOfIncomingTransportMessages.cs b/src/NServiceBus.Core/Unicast/BackwardCompatibility/MutateMessageContentTypeOfIncomingTransportMessages.cs deleted file mode 100644 index 5d4d110ab79..00000000000 --- a/src/NServiceBus.Core/Unicast/BackwardCompatibility/MutateMessageContentTypeOfIncomingTransportMessages.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Unicast.BackwardCompatibility -{ - using MessageMutator; - using Serialization; - using Transport; - - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "6.0", Message = "Exist only for compatibility between V4 and V3. No longer needed in V6")] - class MutateMessageContentTypeOfIncomingTransportMessages : IMutateIncomingTransportMessages, INeedInitialization - { - public IMessageSerializer Serializer { get; set; } - - /// - /// Ensure that the content type which is introduced in V4.0.0 and later versions is present in the header. - /// - /// Transport Message to mutate. - public void MutateIncoming(TransportMessage transportMessage) - { - if (!transportMessage.IsControlMessage() && !transportMessage.Headers.ContainsKey(Headers.ContentType)) - { - transportMessage.Headers[Headers.ContentType] = Serializer.ContentType; - } - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerCall)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/CallbackInvocationBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/CallbackInvocationBehavior.cs deleted file mode 100644 index 7a243a232f3..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/CallbackInvocationBehavior.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Linq; - using NServiceBus.Unicast.Transport; - using Pipeline; - using Pipeline.Contexts; - using Unicast; - - class CallbackInvocationBehavior : IBehavior - { - public const string CallbackInvokedKey = "NServiceBus.CallbackInvocationBehavior.CallbackWasInvoked"; - - public UnicastBus UnicastBus { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var messageWasHandled = HandleCorrelatedMessage(context.PhysicalMessage, context); - - context.Set(CallbackInvokedKey, messageWasHandled); - - next(); - } - - bool HandleCorrelatedMessage(TransportMessage transportMessage, IncomingContext context) - { - if (transportMessage.CorrelationId == null) - { - return false; - } - //All versions of 4 still mutated the intent back to send. So we can only apply this logic for version 5 and above - if (SenderIsV5OrNewer(transportMessage)) - { - if (transportMessage.MessageIntent != MessageIntentEnum.Reply) - { - return false; - } - } - else - { - //older versions used "Send" as intent for replies. Therefor we need to check for id != cid to avoid - // firing callbacks too soon - if (transportMessage.Id == transportMessage.CorrelationId) - { - return false; - } - } - - BusAsyncResult busAsyncResult; - - if (!UnicastBus.messageIdToAsyncResultLookup.TryRemove(transportMessage.CorrelationId, out busAsyncResult)) - { - return false; - } - - var statusCode = int.MinValue; - - if (transportMessage.IsControlMessage()) - { - string errorCode; - if (transportMessage.Headers.TryGetValue(Headers.ReturnMessageErrorCodeHeader, out errorCode)) - { - statusCode = int.Parse(errorCode); - } - } - - busAsyncResult.Complete(statusCode, context.LogicalMessages.Select(lm => lm.Instance).ToArray()); - - return true; - } - - bool SenderIsV5OrNewer(TransportMessage transportMessage) - { - string versionString; - if (!transportMessage.Headers.TryGetValue(Headers.NServiceBusVersion, out versionString)) - { - return false; - } - Version version; - if (!Version.TryParse(versionString, out version)) - { - // if we cant parse the version assume it is not V5 - return false; - } - return version.Major >= 5; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/ChildContainerBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/ChildContainerBehavior.cs deleted file mode 100644 index 402f0819269..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/ChildContainerBehavior.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus -{ - using System; - using ObjectBuilder; - using Pipeline; - using Pipeline.Contexts; - - class ChildContainerBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - using (var childBuilder = context.Builder.CreateChildBuilder()) - { - context.Set(childBuilder); - try - { - next(); - } - finally - { - context.Remove(); - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/CreatePhysicalMessageBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/CreatePhysicalMessageBehavior.cs deleted file mode 100644 index b4ceaefa962..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/CreatePhysicalMessageBehavior.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Unicast.Messages; - using Pipeline; - using Pipeline.Contexts; - using Unicast; - - class CreatePhysicalMessageBehavior : IBehavior - { - public MessageMetadataRegistry MessageMetadataRegistry { get; set; } - - public UnicastBus UnicastBus { get; set; } - - public PipelineExecutor PipelineExecutor { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - var deliveryOptions = context.DeliveryOptions; - - var toSend = new TransportMessage { MessageIntent = MessageIntentEnum.Publish }; - - var sendOptions = deliveryOptions as SendOptions; - - - if (sendOptions != null) - { - toSend.MessageIntent = sendOptions is ReplyOptions ? MessageIntentEnum.Reply : MessageIntentEnum.Send; - - if (sendOptions.CorrelationId != null) - { - toSend.CorrelationId = sendOptions.CorrelationId; - } - } - - //apply static headers - foreach (var kvp in UnicastBus.OutgoingHeaders) - { - toSend.Headers[kvp.Key] = kvp.Value; - } - - //apply individual headers - foreach (var kvp in context.OutgoingLogicalMessage.Headers) - { - toSend.Headers[kvp.Key] = kvp.Value; - } - - if (context.OutgoingLogicalMessage.MessageType != null) - { - var messageDefinitions = MessageMetadataRegistry.GetMessageMetadata(context.OutgoingLogicalMessage.MessageType); - - toSend.TimeToBeReceived = messageDefinitions.TimeToBeReceived; - toSend.Recoverable = messageDefinitions.Recoverable; - } - - context.Set(toSend); - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/DispatchMessageToTransportBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/DispatchMessageToTransportBehavior.cs deleted file mode 100644 index 33e41263ae6..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/DispatchMessageToTransportBehavior.cs +++ /dev/null @@ -1,114 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Unicast; - using NServiceBus.Unicast.Queuing; - using Settings; - using Pipeline; - using Pipeline.Contexts; - using Support; - using Transports; - - class DispatchMessageToTransportBehavior : IBehavior - { - public ISendMessages MessageSender { get; set; } - - public IPublishMessages MessagePublisher { get; set; } - - public IDeferMessages MessageDeferral { get; set; } - - public ReadOnlySettings Settings { get; set; } - - public UnicastBus UnicastBus { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - InvokeNative(context.DeliveryOptions, context.OutgoingMessage); - - next(); - } - - public void InvokeNative(DeliveryOptions deliveryOptions, TransportMessage messageToSend) - { - var messageDescription = "ControlMessage"; - - string enclosedMessageTypes; - - if (messageToSend.Headers.TryGetValue(Headers.EnclosedMessageTypes, out enclosedMessageTypes)) - { - messageDescription = enclosedMessageTypes; - } - - messageToSend.Headers.Add(Headers.OriginatingMachine, RuntimeEnvironment.MachineName); - messageToSend.Headers.Add(Headers.OriginatingEndpoint, Settings.EndpointName()); - messageToSend.Headers.Add(Headers.OriginatingHostId, UnicastBus.HostInformation.HostId.ToString("N")); - - try - { - if(deliveryOptions is PublishOptions) - { - Publish(messageToSend, deliveryOptions as PublishOptions); - } - else - { - SendOrDefer(messageToSend, deliveryOptions as SendOptions); - } - } - catch (QueueNotFoundException ex) - { - throw new Exception(string.Format("The destination queue '{0}' could not be found. You may have misconfigured the destination for this kind of message ({1}) in the MessageEndpointMappings of the UnicastBusConfig section in your configuration file. " + "It may also be the case that the given queue just hasn't been created yet, or has been deleted.", ex.Queue, messageDescription), ex); - } - } - - void SendOrDefer(TransportMessage messageToSend, SendOptions sendOptions) - { - if (sendOptions.DelayDeliveryWith.HasValue) - { - if (sendOptions.DelayDeliveryWith > TimeSpan.Zero) - { - SetIsDeferredHeader(messageToSend); - MessageDeferral.Defer(messageToSend, sendOptions); - } - else - { - MessageSender.Send(messageToSend, sendOptions); - } - - return; - } - - if (sendOptions.DeliverAt.HasValue) - { - var deliverAt = sendOptions.DeliverAt.Value.ToUniversalTime(); - if (deliverAt > DateTime.UtcNow) - { - SetIsDeferredHeader(messageToSend); - MessageDeferral.Defer(messageToSend, sendOptions); - } - else - { - MessageSender.Send(messageToSend, sendOptions); - } - - return; - } - - MessageSender.Send(messageToSend, sendOptions); - } - - static void SetIsDeferredHeader(TransportMessage messageToSend) - { - messageToSend.Headers[Headers.IsDeferredMessage] = true.ToString(); - } - - void Publish(TransportMessage messageToSend,PublishOptions publishOptions) - { - if (MessagePublisher == null) - { - throw new InvalidOperationException("No message publisher has been registered. If you're using a transport without native support for pub/sub please enable the message driven publishing feature by calling config.EnableFeature() in your configuration"); - } - - MessagePublisher.Publish(messageToSend, publishOptions); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/FixSendIntentBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/FixSendIntentBehavior.cs deleted file mode 100644 index 7620b57a27e..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/FixSendIntentBehavior.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - - class FixSendIntentBehavior : IBehavior - { - public void Invoke(OutgoingContext context, Action next) - { - if (context.OutgoingLogicalMessage.Headers.ContainsKey("$.temporary.ReplyToOriginator")) - { - context.OutgoingMessage.MessageIntent = MessageIntentEnum.Reply; - context.OutgoingMessage.Headers.Remove("$.temporary.ReplyToOriginator"); - context.OutgoingLogicalMessage.Headers.Remove("$.temporary.ReplyToOriginator"); - } - - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/ForwardBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/ForwardBehavior.cs deleted file mode 100644 index 56b845185b2..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/ForwardBehavior.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Unicast; - using Pipeline; - using Pipeline.Contexts; - using Transports; - - class ForwardBehavior : IBehavior - { - public IAuditMessages MessageAuditer { get; set; } - - public Address ForwardReceivedMessagesTo { get; set; } - - public TimeSpan? TimeToBeReceivedOnForwardedMessages { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - next(); - - MessageAuditer.Audit(new SendOptions(ForwardReceivedMessagesTo) - { - TimeToBeReceived = TimeToBeReceivedOnForwardedMessages - }, context.PhysicalMessage); - - } - - public class Registration : RegisterStep - { - public Registration() - : base("ForwardMessageTo", typeof(ForwardBehavior), "Forwards message to the specified queue in the UnicastBus config section.") - { - InsertBefore(WellKnownStep.ExecuteUnitOfWork); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/ForwardReceivedMessages.cs b/src/NServiceBus.Core/Unicast/Behaviors/ForwardReceivedMessages.cs deleted file mode 100644 index 7b4ccc1710e..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/ForwardReceivedMessages.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using NServiceBus.Config; - using NServiceBus.Unicast.Queuing.Installers; - - /// - /// Provides message forwarding capabilities - /// - public class ForwardReceivedMessages : Feature - { - internal ForwardReceivedMessages() - { - EnableByDefault(); - // Only enable if the configuration is defined in UnicastBus - Prerequisite(config => GetConfiguredForwardMessageQueue(config) != Address.Undefined,"No forwarding address was defined in the unicastbus config"); - } - - /// - /// Invoked if the feature is activated - /// - /// The feature context - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Pipeline.Register(); - - var forwardReceivedMessagesQueue = GetConfiguredForwardMessageQueue(context); - var timeToBeReceived = GetConfiguredTimeToBeReceivedValue(context); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.Enabled, true) - .ConfigureProperty(t => t.Address, forwardReceivedMessagesQueue); - - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall) - .ConfigureProperty(p => p.ForwardReceivedMessagesTo, forwardReceivedMessagesQueue) - .ConfigureProperty(p => p.TimeToBeReceivedOnForwardedMessages, timeToBeReceived); - } - - TimeSpan? GetConfiguredTimeToBeReceivedValue(FeatureConfigurationContext context) - { - var unicastBusConfig = context.Settings.GetConfigSection(); - if (unicastBusConfig != null && unicastBusConfig.TimeToBeReceivedOnForwardedMessages > TimeSpan.Zero) - { - return unicastBusConfig.TimeToBeReceivedOnForwardedMessages; - } - return TimeSpan.MaxValue; - } - Address GetConfiguredForwardMessageQueue(FeatureConfigurationContext context) - { - var unicastBusConfig = context.Settings.GetConfigSection(); - if (unicastBusConfig != null && !string.IsNullOrWhiteSpace(unicastBusConfig.ForwardReceivedMessagesTo)) - { - return Address.Parse(unicastBusConfig.ForwardReceivedMessagesTo); - } - return Address.Undefined; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/InvokeHandlersBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/InvokeHandlersBehavior.cs deleted file mode 100644 index 55d3c6d3bbe..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/InvokeHandlersBehavior.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - using Sagas; - - class InvokeHandlersBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - ActiveSagaInstance saga; - - if (context.TryGet(out saga) && saga.NotFound && saga.SagaType == context.MessageHandler.Instance.GetType()) - { - next(); - return; - } - - var messageHandler = context.MessageHandler; - - messageHandler.Invocation(messageHandler.Instance, context.IncomingLogicalMessage.Instance); - next(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/InvokeSagaNotFoundBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/InvokeSagaNotFoundBehavior.cs deleted file mode 100644 index d3cea25d321..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/InvokeSagaNotFoundBehavior.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Logging; - using NServiceBus.Pipeline; - using NServiceBus.Pipeline.Contexts; - using NServiceBus.Saga; - - class InvokeSagaNotFoundBehavior : IBehavior - { - static ILog logger = LogManager.GetLogger(); - - public void Invoke(IncomingContext context, Action next) - { - context.Set("Sagas.InvokeSagaNotFound", false); - - next(); - - if (!context.Get("Sagas.InvokeSagaNotFound")) - { - return; - } - - bool result; - if (context.TryGet("Sagas.SagaWasInvoked", out result)) - { - if (result) - { - return; - } - } - - logger.InfoFormat("Could not find a started saga for '{0}' message type. Going to invoke SagaNotFoundHandlers.", context.IncomingLogicalMessage.MessageType.FullName); - - foreach (var handler in context.Builder.BuildAll()) - { - logger.DebugFormat("Invoking SagaNotFoundHandler ('{0}')", handler.GetType().FullName); - handler.Handle(context.IncomingLogicalMessage.Instance); - } - } - - public class Registration : RegisterStep - { - public Registration() - : base("InvokeSagaNotFound", typeof(InvokeSagaNotFoundBehavior), "Invokes saga not found logic") - { - InsertBefore(WellKnownStep.LoadHandlers); - InsertAfter(WellKnownStep.MutateIncomingMessages); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/LoadHandlersBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/LoadHandlersBehavior.cs deleted file mode 100644 index 9b72620ead9..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/LoadHandlersBehavior.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Linq; - using MessageInterfaces; - using NServiceBus.Unicast.Behaviors; - using Pipeline; - using Pipeline.Contexts; - using Unicast; - - class LoadHandlersBehavior : IBehavior - { - public IMessageHandlerRegistry HandlerRegistry { get; set; } - - public IMessageMapper MessageMapper { get; set; } - - public PipelineExecutor PipelineFactory { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var messageToHandle = context.IncomingLogicalMessage; - - // for now we cheat and pull it from the behavior context: - var callbackInvoked = context.Get(CallbackInvocationBehavior.CallbackInvokedKey); - var handlerTypedToInvoke = HandlerRegistry.GetHandlerTypes(messageToHandle.MessageType).ToList(); - - if (!callbackInvoked && !handlerTypedToInvoke.Any()) - { - var error = string.Format("No handlers could be found for message type: {0}", messageToHandle.MessageType); - throw new InvalidOperationException(error); - } - - foreach (var handlerType in handlerTypedToInvoke) - { - using (context.CreateSnapshotRegion()) - { - var loadedHandler = new MessageHandler - { - Instance = context.Builder.Build(handlerType), - Invocation = (handlerInstance, message) => HandlerRegistry.InvokeHandle(handlerInstance, message) - }; - - context.MessageHandler = loadedHandler; - - next(); - - if (context.HandlerInvocationAborted) - { - //if the chain was aborted skip the other handlers - break; - } - } - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/LogOutgoingMessageBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/LogOutgoingMessageBehavior.cs deleted file mode 100644 index 273ae3bd259..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/LogOutgoingMessageBehavior.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Linq; - using Logging; - using NServiceBus.Unicast; - using Pipeline; - using Pipeline.Contexts; - - class LogOutgoingMessageBehavior : IBehavior - { - public void Invoke(OutgoingContext context, Action next) - { - var options = context.DeliveryOptions as SendOptions; - - if (options != null && log.IsDebugEnabled && context.OutgoingLogicalMessage != null) - { - var destination = options.Destination.ToString(); - - log.DebugFormat("Sending message '{0}' with id '{1}' to destination '{2}'.\n" + - "ToString() of the message yields: {3}\n" + - "Message headers:\n{4}", - context.OutgoingLogicalMessage.MessageType != null ? context.OutgoingLogicalMessage.MessageType.AssemblyQualifiedName : "unknown", - context.OutgoingMessage.Id, - destination, - context.OutgoingLogicalMessage.Instance, - string.Join(", ", context.OutgoingLogicalMessage.Headers.Select(h => h.Key + ":" + h.Value).ToArray())); - } - - next(); - - } - - static ILog log = LogManager.GetLogger("LogOutgoingMessage"); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/MessageHandler.cs b/src/NServiceBus.Core/Unicast/Behaviors/MessageHandler.cs deleted file mode 100644 index 92daff65555..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/MessageHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Unicast.Behaviors -{ - using System; - - /// - /// Represents a message handler and its invocation - /// - public class MessageHandler - { - /// - /// The actual instance, can be a saga or just a plain handler - /// - public object Instance { get; set; } - - /// - /// The actual invocation - /// - public Action Invocation { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/SendValidatorBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/SendValidatorBehavior.cs deleted file mode 100644 index 08249d69e58..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/SendValidatorBehavior.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Unicast.Transport; - using Pipeline; - using Pipeline.Contexts; - using Unicast; - - class SendValidatorBehavior : IBehavior - { - public Conventions Conventions { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - if (!context.OutgoingLogicalMessage.IsControlMessage()) - { - VerifyBestPractices(context); - } - - next(); - } - - void VerifyBestPractices(OutgoingContext context) - { - if (!context.DeliveryOptions.EnforceMessagingBestPractices) - { - return; - } - - var sendOptions = context.DeliveryOptions as SendOptions; - - if (sendOptions == null) - { - MessagingBestPractices.AssertIsValidForPubSub(context.OutgoingLogicalMessage.MessageType, Conventions); - return; - } - - if (sendOptions.Destination == Address.Undefined) - { - throw new InvalidOperationException("No destination specified for message: " + context.OutgoingLogicalMessage.MessageType); - } - - if (sendOptions is ReplyOptions) - { - MessagingBestPractices.AssertIsValidForReply(context.OutgoingLogicalMessage.MessageType, Conventions); - } - else - { - MessagingBestPractices.AssertIsValidForSend(context.OutgoingLogicalMessage.MessageType, Conventions); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Behaviors/SerializeMessagesBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/SerializeMessagesBehavior.cs deleted file mode 100644 index f91fdccf850..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/SerializeMessagesBehavior.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.IO; - using System.Linq; - using NServiceBus.Unicast.Messages; - using NServiceBus.Unicast.Transport; - using Pipeline; - using Pipeline.Contexts; - using Serialization; - - class SerializeMessagesBehavior : IBehavior - { - public IMessageSerializer MessageSerializer { get; set; } - - public void Invoke(OutgoingContext context, Action next) - { - if (!context.OutgoingMessage.IsControlMessage()) - { - using (var ms = new MemoryStream()) - { - - MessageSerializer.Serialize(context.OutgoingLogicalMessage.Instance, ms); - - context.OutgoingMessage.Headers[Headers.ContentType] = MessageSerializer.ContentType; - - context.OutgoingMessage.Headers[Headers.EnclosedMessageTypes] = SerializeEnclosedMessageTypes(context.OutgoingLogicalMessage); - - context.OutgoingMessage.Body = ms.ToArray(); - } - - foreach (var headerEntry in context.OutgoingLogicalMessage.Headers) - { - context.OutgoingMessage.Headers[headerEntry.Key] = headerEntry.Value; - } - } - - next(); - } - - string SerializeEnclosedMessageTypes(LogicalMessage message) - { - var distinctTypes = message.Metadata.MessageHierarchy.Distinct(); - - return string.Join(";", distinctTypes.Select(t => t.AssemblyQualifiedName)); - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Behaviors/SetCurrentMessageBeingHandledBehavior.cs b/src/NServiceBus.Core/Unicast/Behaviors/SetCurrentMessageBeingHandledBehavior.cs deleted file mode 100644 index c0d9dd9b52a..00000000000 --- a/src/NServiceBus.Core/Unicast/Behaviors/SetCurrentMessageBeingHandledBehavior.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus -{ - using System; - using Pipeline; - using Pipeline.Contexts; - - class SetCurrentMessageBeingHandledBehavior : IBehavior - { - public void Invoke(IncomingContext context, Action next) - { - var logicalMessage = context.IncomingLogicalMessage; - - try - { - ExtensionMethods.CurrentMessageBeingHandled = logicalMessage.Instance; - - next(); - } - finally - { - ExtensionMethods.CurrentMessageBeingHandled = null; - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/BuilderExtensions.cs b/src/NServiceBus.Core/Unicast/BuilderExtensions.cs index b0d72de1035..e274b464c5f 100644 --- a/src/NServiceBus.Core/Unicast/BuilderExtensions.cs +++ b/src/NServiceBus.Core/Unicast/BuilderExtensions.cs @@ -4,15 +4,17 @@ namespace NServiceBus.Unicast using ObjectBuilder; /// - /// Extension methods for IBuilder + /// Extension methods for . /// public static class BuilderExtensions { /// - /// Applies the action on the instances of T + /// Applies the action on the instances of . /// public static void ForEach(this IBuilder builder, Action action) { + Guard.AgainstNull(nameof(builder), builder); + Guard.AgainstNull(nameof(action), action); foreach (var t in builder.BuildAll()) { action(t); diff --git a/src/NServiceBus.Core/Unicast/BusAsyncResult.cs b/src/NServiceBus.Core/Unicast/BusAsyncResult.cs deleted file mode 100644 index b3181e709f3..00000000000 --- a/src/NServiceBus.Core/Unicast/BusAsyncResult.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using System.Threading; - using Logging; - - /// - /// Implementation of IAsyncResult returned when registering a callback. - /// - public class BusAsyncResult : IAsyncResult - { - readonly AsyncCallback callback; - readonly CompletionResult result; - volatile bool completed; - readonly ManualResetEvent sync; - - /// - /// Creates a new object storing the given callback and state. - /// - public BusAsyncResult(AsyncCallback callback, object state) - { - this.callback = callback; - result = new CompletionResult - { - State = state - }; - sync = new ManualResetEvent(false); - } - - /// - /// Stores the given error code and messages, - /// releases any blocked threads, - /// and invokes the previously given callback. - /// - public void Complete(int errorCode, params object[] messages) - { - result.ErrorCode = errorCode; - result.Messages = messages; - completed = true; - - if (callback != null) - try - { - callback(this); - } - catch (Exception e) - { - log.Error(callback.ToString(), e); - } - - sync.Set(); - } - - static ILog log = LogManager.GetLogger(); - - /// - /// Returns a completion result containing the error code, messages, and state. - /// - public object AsyncState - { - get { return result; } - } - - /// - /// Returns a handle suitable for blocking threads. - /// - public WaitHandle AsyncWaitHandle - { - get { return sync; } - } - - /// - /// Returns false. - /// - public bool CompletedSynchronously - { - get { return false; } - } - - /// - /// Returns if the operation has completed. - /// - public bool IsCompleted - { - get { return completed; } - } - } -} diff --git a/src/NServiceBus.Core/Unicast/BusAsyncResultEventArgs.cs b/src/NServiceBus.Core/Unicast/BusAsyncResultEventArgs.cs deleted file mode 100644 index 446abc8e0bd..00000000000 --- a/src/NServiceBus.Core/Unicast/BusAsyncResultEventArgs.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NServiceBus -{ - using System; - using NServiceBus.Unicast; - - /// - /// Argument passed in the Registered event of the Callback object. - /// - public class BusAsyncResultEventArgs : EventArgs - { - /// - /// Gets/sets the IAsyncResult. - /// - public BusAsyncResult Result { get; set; } - - /// - /// Gets/sets the message id. - /// - public string MessageId { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Callback.cs b/src/NServiceBus.Core/Unicast/Callback.cs deleted file mode 100644 index edd409d315d..00000000000 --- a/src/NServiceBus.Core/Unicast/Callback.cs +++ /dev/null @@ -1,213 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Globalization; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using System.Web; - using System.Web.UI; - using Unicast; - - /// - /// Implementation of the interface for the unicast bus. - /// - class Callback : ICallback - { - static Type AsyncControllerType; - - string messageId; - bool isSendOnly; - - static Callback() - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in assemblies.Where(assembly => assembly.GetName().Name == "System.Web.Mvc")) - { - AsyncControllerType = assembly.GetType("System.Web.Mvc.AsyncController", false); - } - - if (AsyncControllerType == null) - { - //We just initialize it to any type so we don't need to check for nulls. - AsyncControllerType = typeof(BusAsyncResultEventArgs); - } - } - - /// - /// Creates a new instance of the callback object storing the given message id. - /// - public Callback(string messageId, bool isSendOnly) - { - this.messageId = messageId; - this.isSendOnly = isSendOnly; - } - - /// - /// Event raised when the Register method is called. - /// - public event EventHandler Registered; - - /// - /// Returns the message id this object was constructed with. - /// - public string MessageId - { - get { return messageId; } - } - - public Task Register() - { - var asyncResult = Register(null, null); - var task = Task.Factory.FromAsync(asyncResult, x => - { - var cr = ((CompletionResult) x.AsyncState); - - return cr.ErrorCode; - }, TaskCreationOptions.None, TaskScheduler.Default); - - return task; - } - - public Task Register() - { - if (!typeof (T).IsEnum) - throw new InvalidOperationException( - "Register can only be used with enumerations, use Register() to return an integer instead"); - - var asyncResult = Register(null, null); - var task = Task.Factory.FromAsync(asyncResult, x => - { - var cr = ((CompletionResult) x.AsyncState); - - return (T) Enum.Parse(typeof (T), cr.ErrorCode.ToString(CultureInfo.InvariantCulture)); - }, TaskCreationOptions.None, TaskScheduler.Default); - - return task; - } - - public Task Register(Func completion) - { - var asyncResult = Register(null, null); - return Task.Factory.FromAsync(asyncResult, x => completion((CompletionResult) x.AsyncState), - TaskCreationOptions.None, TaskScheduler.Default); - } - - public Task Register(Action completion) - { - var asyncResult = Register(null, null); - return Task.Factory.FromAsync(asyncResult, x => completion((CompletionResult) x.AsyncState), - TaskCreationOptions.None, TaskScheduler.Default); - } - - public IAsyncResult Register(AsyncCallback callback, object state) - { - if (isSendOnly) - { - throw new Exception("Callbacks are invalid in a sendonly endpoint."); - } - var result = new BusAsyncResult(callback, state); - - if (Registered != null) - Registered(this, new BusAsyncResultEventArgs { Result = result, MessageId = messageId }); - - return result; - } - - public void Register(Action callback) - { - var page = callback.Target as Page; - if (page != null) - { - Register(callback, page); - return; - } - - if (AsyncControllerType.IsInstanceOfType(callback.Target)) - { - Register(callback, callback.Target); - return; - } - - var context = SynchronizationContext.Current; - Register(callback, context); - } - - public void Register(Action callback, object synchronizer) - { - if (!typeof(T).IsEnum && typeof(T) != typeof(int)) - throw new InvalidOperationException("Can only support registering callbacks for integer or enum types. The given type is: " + typeof(T).FullName); - - if (HttpContext.Current != null && synchronizer == null) - throw new ArgumentNullException("synchronizer", "NServiceBus has detected that you're running in a web context but have passed in a null synchronizer. Please pass in a reference to a System.Web.UI.Page or a System.Web.Mvc.AsyncController."); - - if (synchronizer == null) - { - Register(GetCallbackInvocationActionFrom(callback), null); - return; - } - - var page = synchronizer as Page; - if (page != null) - { - page.RegisterAsyncTask(new PageAsyncTask( - (sender, e, cb, extraData) => Register(cb, extraData), - new EndEventHandler(GetCallbackInvocationActionFrom(callback)), - null, - null - )); - return; - } - - if (AsyncControllerType.IsInstanceOfType(synchronizer)) - { - dynamic asyncController = synchronizer; - asyncController.AsyncManager.OutstandingOperations.Increment(); - - Register(GetMvcCallbackInvocationActionFrom(callback, asyncController.AsyncManager), null); - - return; - } - - var synchronizationContext = synchronizer as SynchronizationContext; - if (synchronizationContext != null) - { - Register( - ar => synchronizationContext.Post( - x => GetCallbackInvocationActionFrom(callback).Invoke(ar), null), - null - ); - } - } - - static AsyncCallback GetMvcCallbackInvocationActionFrom(Action callback, dynamic am) - { - return asyncResult => - { - HandleAsyncResult(callback, asyncResult); - am.OutstandingOperations.Decrement(); - }; - } - - static AsyncCallback GetCallbackInvocationActionFrom(Action callback) - { - return asyncResult => HandleAsyncResult(callback, asyncResult); - } - - static void HandleAsyncResult(Action callback, IAsyncResult asyncResult) - { - var cr = asyncResult.AsyncState as CompletionResult; - if (cr == null) return; - - var action = callback as Action; - if (action != null) - { - action.Invoke(cr.ErrorCode); - } - else - { - callback((T)Enum.ToObject(typeof(T), cr.ErrorCode)); - } - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtensions.cs b/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtensions.cs new file mode 100644 index 00000000000..7e2b4691b3b --- /dev/null +++ b/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtensions.cs @@ -0,0 +1,92 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using Features; + + /// + /// Provides configuration options to tune handler ordering. + /// + public static class LoadMessageHandlersExtensions + { + /// + /// Loads all message handler assemblies in the runtime directory + /// and specifies that handlers in the given assembly should run + /// before all others. + /// Use First{T} to indicate the type to load from. + /// + /// The instance to apply the settings to. + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "ExecuteTheseHandlersFirst")] + public static void LoadMessageHandlers(this EndpointConfiguration config) + { + throw new NotImplementedException(); + } + + /// + /// Loads all message handler assemblies in the runtime directory + /// and specifies that the handlers in the given 'order' are to + /// run before all others and in the order specified. + /// + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "ExecuteTheseHandlersFirst")] + public static void LoadMessageHandlers(this EndpointConfiguration config, First order) + { + throw new NotImplementedException(); + } + + /// + /// Loads all message handler assemblies in the runtime directory + /// and specifies that the handlers in the given 'order' are to + /// run before all others and in the order specified. + /// + /// The instance to apply the settings to. + /// The handler types to execute first. + public static void ExecuteTheseHandlersFirst(this EndpointConfiguration config, IEnumerable handlerTypes) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(handlerTypes), handlerTypes); + + List list; + if (!config.Settings.TryGet("NServiceBus.ExecuteTheseHandlersFirst", out list)) + { + list = new List(); + config.Settings.Set("NServiceBus.ExecuteTheseHandlersFirst", list); + } + + foreach (var handlerType in handlerTypes) + { + if (!RegisterHandlersInOrder.IsMessageHandler(handlerType)) + { + throw new ArgumentException($"'{handlerType}' is not a handler type, ensure that all types derive from IHandleMessages"); + } + + if (list.Contains(handlerType)) + { + throw new ArgumentException($"The order in which the type '{handlerType}' should be invoked was already specified by a previous call. A handler type can only specified once."); + } + + list.Add(handlerType); + } + } + + /// + /// Loads all message handler assemblies in the runtime directory + /// and specifies that the handlers in the given 'order' are to + /// run before all others and in the order specified. + /// + /// The instance to apply the settings to. + /// The handler types to execute first. + public static void ExecuteTheseHandlersFirst(this EndpointConfiguration config, params Type[] handlerTypes) + { + Guard.AgainstNull(nameof(config), config); + Guard.AgainstNull(nameof(handlerTypes), handlerTypes); + + config.ExecuteTheseHandlersFirst((IEnumerable) handlerTypes); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions.cs b/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions.cs deleted file mode 100644 index fd25ee2bf53..00000000000 --- a/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NServiceBus -{ - using System; - - /// - /// Provides configuration options to tune handler ordering - /// - public static partial class LoadMessageHandlersExtentions - { - /// - /// Loads all message handler assemblies in the runtime directory - /// and specifies that handlers in the given assembly should run - /// before all others. - /// Use First{T} to indicate the type to load from. - /// - public static void LoadMessageHandlers(this BusConfiguration config) - { - var args = typeof(TFirst).GetGenericArguments(); - if (args.Length == 1) - { - if (typeof(First<>).MakeGenericType(args[0]).IsAssignableFrom(typeof(TFirst))) - { - config.Settings.Set("LoadMessageHandlers.Order.Types", new[] { args[0] }); - return; - } - } - - throw new ArgumentException("TFirst should be of the type First where T is the type to indicate as first."); - } - - /// - /// Loads all message handler assemblies in the runtime directory - /// and specifies that the handlers in the given 'order' are to - /// run before all others and in the order specified. - /// - public static void LoadMessageHandlers(this BusConfiguration config, First order) - { - config.Settings.Set("LoadMessageHandlers.Order.Types", order.Types); - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions_Obsolete.cs b/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions_Obsolete.cs deleted file mode 100644 index 432997c21ac..00000000000 --- a/src/NServiceBus.Core/Unicast/Config/LoadMessageHandlersExtentions_Obsolete.cs +++ /dev/null @@ -1,36 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - - public static partial class LoadMessageHandlersExtentions - { - [ObsoleteEx( - Message = "It is safe to remove this method call. This is the default behavior.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure LoadMessageHandlers(this Configure config) - { - throw new InvalidOperationException(); - } - - [ObsoleteEx( - Message = "Use `configuration.LoadMessageHandlers`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure LoadMessageHandlers(this Configure config) - { - throw new InvalidOperationException(); - } - - [ObsoleteEx( - Message = "Use `configuration.LoadMessageHandlers`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure LoadMessageHandlers(this Configure config, First order) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Config/PathUtilities.cs b/src/NServiceBus.Core/Unicast/Config/PathUtilities.cs deleted file mode 100644 index 9e2c9708db0..00000000000 --- a/src/NServiceBus.Core/Unicast/Config/PathUtilities.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Features -{ - using System.Linq; - using System.Text.RegularExpressions; - - internal static class PathUtilities - { - public static string SanitizedPath(string commandLine) - { - if (commandLine.StartsWith("\"")) - { - return (from Match match in Regex.Matches(commandLine, "\"([^\"]*)\"") - select match.ToString()).First().Trim('"'); - } - - return commandLine.Split(' ').First(); - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Config/RegisterHandlersInOrder.cs b/src/NServiceBus.Core/Unicast/Config/RegisterHandlersInOrder.cs index f93f9b21b39..c6dbdaaddf4 100644 --- a/src/NServiceBus.Core/Unicast/Config/RegisterHandlersInOrder.cs +++ b/src/NServiceBus.Core/Unicast/Config/RegisterHandlersInOrder.cs @@ -2,12 +2,9 @@ namespace NServiceBus.Features { using System; using System.Collections.Generic; - using System.Configuration; using System.Linq; - using NServiceBus.Logging; - using NServiceBus.ObjectBuilder; - using NServiceBus.Settings; - using NServiceBus.Unicast; + using ObjectBuilder; + using Unicast; class RegisterHandlersInOrder : Feature { @@ -18,52 +15,22 @@ public RegisterHandlersInOrder() protected internal override void Setup(FeatureConfigurationContext context) { - if (context.Container.HasComponent()) + if (context.Container.HasComponent()) { return; } - IEnumerable order; + List order; - if (!context.Settings.TryGet("LoadMessageHandlers.Order.Types", out order)) + if (!context.Settings.TryGet("NServiceBus.ExecuteTheseHandlersFirst", out order)) { - order = ISpecifyMessageHandlerOrdering(context.Settings); + order = new List(0); } LoadMessageHandlers(context, order); } - static IEnumerable ISpecifyMessageHandlerOrdering(ReadOnlySettings settings) - { - var types = new List(); - - foreach (var t in settings.GetAvailableTypes().Where(TypeSpecifiesMessageHandlerOrdering)) - { - Logger.DebugFormat("Going to ask for message handler ordering from '{0}'.", t); - - var order = new Order(); - ((ISpecifyMessageHandlerOrdering)Activator.CreateInstance(t)).SpecifyOrder(order); - - foreach (var ht in order.Types) - { - if (types.Contains(ht)) - { - throw new ConfigurationErrorsException(string.Format("The order in which the type '{0}' should be invoked was already specified by a previous implementor of ISpecifyMessageHandlerOrdering. Check the debug logs to see which other specifiers have been invoked.", ht)); - } - } - - types.AddRange(order.Types); - } - - return types; - } - - static bool TypeSpecifiesMessageHandlerOrdering(Type t) - { - return typeof(ISpecifyMessageHandlerOrdering).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface; - } - - static void LoadMessageHandlers(FeatureConfigurationContext context, IEnumerable orderedTypes) + static void LoadMessageHandlers(FeatureConfigurationContext context, List orderedTypes) { var types = new List(context.Settings.GetAvailableTypes()); @@ -80,13 +47,11 @@ static void LoadMessageHandlers(FeatureConfigurationContext context, IEnumerable static void ConfigureMessageHandlersIn(FeatureConfigurationContext context, IEnumerable types) { var handlerRegistry = new MessageHandlerRegistry(context.Settings.Get()); - var handlers = new List(); foreach (var t in types.Where(IsMessageHandler)) { context.Container.ConfigureComponent(t, DependencyLifecycle.InstancePerUnitOfWork); handlerRegistry.RegisterHandler(t); - handlers.Add(t); } List> propertiesToInject; @@ -98,7 +63,7 @@ static void ConfigureMessageHandlersIn(FeatureConfigurationContext context, IEnu } } - context.Container.RegisterSingleton(handlerRegistry); + context.Container.RegisterSingleton(handlerRegistry); } public static bool IsMessageHandler(Type type) @@ -109,30 +74,11 @@ public static bool IsMessageHandler(Type type) } return type.GetInterfaces() - .Select(GetMessageTypeFromMessageHandler) - .Any(messageType => messageType != null); - } - - static Type GetMessageTypeFromMessageHandler(Type t) - { - if (t.IsGenericType) - { - var args = t.GetGenericArguments(); - if (args.Length != 1) - { - return null; - } - - var handlerType = typeof(IHandleMessages<>).MakeGenericType(args[0]); - if (handlerType.IsAssignableFrom(t)) - { - return args[0]; - } - } - - return null; + .Where(@interface => @interface.IsGenericType) + .Select(@interface => @interface.GetGenericTypeDefinition()) + .Any(genericTypeDef => genericTypeDef == IHandleMessagesType); } - static ILog Logger = LogManager.GetLogger(); + static Type IHandleMessagesType = typeof(IHandleMessages<>); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Config/UnicastBus.cs b/src/NServiceBus.Core/Unicast/Config/UnicastBus.cs deleted file mode 100644 index 4e0d2ac7edf..00000000000 --- a/src/NServiceBus.Core/Unicast/Config/UnicastBus.cs +++ /dev/null @@ -1,229 +0,0 @@ -namespace NServiceBus.Features -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using AutomaticSubscriptions; - using Config; - using Faults; - using Logging; - using NServiceBus.Hosting; - using NServiceBus.Support; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; - using NServiceBus.Utils; - using Pipeline; - using Pipeline.Contexts; - using Transports; - using Unicast.Messages; - using Unicast.Routing; - using Unicast.Transport; - - class UnicastBus : Feature - { - internal UnicastBus() - { - EnableByDefault(); - - Defaults(s => - { - var fullPathToStartingExe = PathUtilities.SanitizedPath(Environment.CommandLine); - - if (!s.HasExplicitValue("NServiceBus.HostInformation.HostId")) - { - s.SetDefault("NServiceBus.HostInformation.HostId", DeterministicGuid.Create(fullPathToStartingExe, RuntimeEnvironment.MachineName)); - } - s.SetDefault("NServiceBus.HostInformation.DisplayName", RuntimeEnvironment.MachineName); - s.SetDefault("NServiceBus.HostInformation.Properties", new Dictionary - { - {"Machine", RuntimeEnvironment.MachineName}, - {"ProcessID", Process.GetCurrentProcess().Id.ToString()}, - {"UserName", Environment.UserName}, - {"PathToExecutable", fullPathToStartingExe} - }); - }); - } - - - protected internal override void Setup(FeatureConfigurationContext context) - { - var defaultAddress = context.Settings.LocalAddress(); - var hostInfo = new HostInformation(context.Settings.Get("NServiceBus.HostInformation.HostId"), - context.Settings.Get("NServiceBus.HostInformation.DisplayName"), - context.Settings.Get>("NServiceBus.HostInformation.Properties")); - - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance) - .ConfigureProperty(u => u.InputAddress, defaultAddress) - .ConfigureProperty(u => u.HostInformation, hostInfo); - - ConfigureSubscriptionAuthorization(context); - - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - ConfigureBehaviors(context); - - var knownMessages = context.Settings.GetAvailableTypes() - .Where(context.Settings.Get().IsMessageType) - .ToList(); - - RegisterMessageOwnersAndBusAddress(context, knownMessages); - - ConfigureMessageRegistry(context, knownMessages); - - if (context.Settings.GetOrDefault("Endpoint.SendOnly")) - { - return; - } - - SetTransportThresholds(context); - } - - void SetTransportThresholds(FeatureConfigurationContext context) - { - var transportConfig = context.Settings.GetConfigSection(); - var maximumThroughput = 0; - var maximumNumberOfRetries = 5; - var maximumConcurrencyLevel = 1; - - if (transportConfig != null) - { - maximumNumberOfRetries = transportConfig.MaxRetries; - maximumThroughput = transportConfig.MaximumMessageThroughputPerSecond; - maximumConcurrencyLevel = transportConfig.MaximumConcurrencyLevel; - } - - var transactionSettings = new TransactionSettings(context.Settings) - { - MaxRetries = maximumNumberOfRetries - }; - - context.Container.ConfigureComponent(b => new TransportReceiver(transactionSettings, maximumConcurrencyLevel, maximumThroughput, b.Build(), b.Build(), context.Settings, b.Build()) - { - CriticalError = b.Build(), - Notifications = b.Build() - }, DependencyLifecycle.InstancePerCall); - } - - void ConfigureBehaviors(FeatureConfigurationContext context) - { - // ReSharper disable HeapView.SlowDelegateCreation - var behaviorTypes = context.Settings.GetAvailableTypes().Where(t => (typeof(IBehavior).IsAssignableFrom(t) || typeof(IBehavior).IsAssignableFrom(t)) - && !(t.IsAbstract || t.IsInterface)); - // ReSharper restore HeapView.SlowDelegateCreation - foreach (var behaviorType in behaviorTypes) - { - context.Container.ConfigureComponent(behaviorType, DependencyLifecycle.InstancePerCall); - } - } - - void ConfigureSubscriptionAuthorization(FeatureConfigurationContext context) - { - var typeToUse = FindAuthorizationType(context.Settings.GetAvailableTypes()); - context.Container.ConfigureComponent(typeToUse, DependencyLifecycle.SingleInstance); - } - - internal static Type FindAuthorizationType(IEnumerable availableTypes) - { - var authType = typeof(IAuthorizeSubscriptions); - var noopType = typeof(NoopSubscriptionAuthorizer); - var foundAuthTypes = availableTypes - .Where(t => t != noopType && authType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) - .ToList(); - if (foundAuthTypes.Count > 1) - { - var fullNames = foundAuthTypes.Select(type => type.FullName); - var error = string.Format("Only one instance of IAuthorizeSubscriptions is allowed. Found the following: '{0}'.", string.Join("', '", fullNames)); - throw new Exception(error); - } - if (foundAuthTypes.Count == 0) - { - return noopType; - } - return foundAuthTypes.Single(); - } - - void RegisterMessageOwnersAndBusAddress(FeatureConfigurationContext context, IEnumerable knownMessages) - { - var unicastConfig = context.Settings.GetConfigSection(); - var router = new StaticMessageRouter(knownMessages); - var key = typeof(AutoSubscriptionStrategy).FullName + ".SubscribePlainMessages"; - - if (context.Settings.HasSetting(key)) - { - router.SubscribeToPlainMessages = context.Settings.Get(key); - } - - context.Container.RegisterSingleton(router); - - if (unicastConfig == null) - { - return; - } - - if (!string.IsNullOrWhiteSpace(unicastConfig.ForwardReceivedMessagesTo)) - { - var forwardAddress = Address.Parse(unicastConfig.ForwardReceivedMessagesTo); - context.Container.ConfigureProperty(b => b.ForwardReceivedMessagesTo, forwardAddress); - } - - if (unicastConfig.TimeToBeReceivedOnForwardedMessages != TimeSpan.Zero) - { - context.Container.ConfigureProperty(b => b.TimeToBeReceivedOnForwardedMessages, unicastConfig.TimeToBeReceivedOnForwardedMessages); - } - - var messageEndpointMappings = unicastConfig.MessageEndpointMappings.Cast() - .OrderByDescending(m => m) - .ToList(); - - foreach (var mapping in messageEndpointMappings) - { - mapping.Configure((messageType, address) => - { - var conventions = context.Settings.Get(); - if (!(conventions.IsMessageType(messageType) || conventions.IsEventType(messageType) || conventions.IsCommandType(messageType))) - { - return; - } - - if (conventions.IsEventType(messageType)) - { - router.RegisterEventRoute(messageType, address); - return; - } - - router.RegisterMessageRoute(messageType, address); - }); - } - } - void ConfigureMessageRegistry(FeatureConfigurationContext context, IEnumerable knownMessages) - { - var messageRegistry = new MessageMetadataRegistry(!DurableMessagesConfig.GetDurableMessagesEnabled(context.Settings), context.Settings.Get()); - - foreach (var msg in knownMessages) - { - messageRegistry.RegisterMessageType(msg); - } - - context.Container.RegisterSingleton(messageRegistry); - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - - if (!Logger.IsInfoEnabled) - { - return; - } - - var messageDefinitions = messageRegistry.GetAllMessages().ToList(); - - Logger.InfoFormat("Number of messages found: {0}", messageDefinitions.Count()); - - if (!Logger.IsDebugEnabled) - { - return; - } - - Logger.DebugFormat("Message definitions: \n {0}", - string.Concat(messageDefinitions.Select(md => md.ToString() + "\n"))); - } - - static ILog Logger = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/DeliveryOptions.cs b/src/NServiceBus.Core/Unicast/DeliveryOptions.cs deleted file mode 100644 index d6ef0bff36f..00000000000 --- a/src/NServiceBus.Core/Unicast/DeliveryOptions.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.Unicast -{ - /// - /// Base class for options to deliver messages. - /// - public abstract class DeliveryOptions - { - /// - /// Creates an instance of . - /// - protected DeliveryOptions() - { - EnforceMessagingBestPractices = true; - EnlistInReceiveTransaction = true; - } - - /// - /// If set messaging best practices will be enforces (on by default) - /// - public bool EnforceMessagingBestPractices { get; set; } - - /// - /// Tells the transport to enlist the outgoing operation in the current receive transaction if possible. - /// This is enabled by default - /// - public bool EnlistInReceiveTransaction { get; set; } - - /// - /// The reply address to use for outgoing messages - /// - public Address ReplyToAddress { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/IMessageDispatcherFactory_Obsolete.cs b/src/NServiceBus.Core/Unicast/IMessageDispatcherFactory_Obsolete.cs deleted file mode 100644 index 8ede4494aab..00000000000 --- a/src/NServiceBus.Core/Unicast/IMessageDispatcherFactory_Obsolete.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using System.Collections.Generic; - using NServiceBus.ObjectBuilder; - - /// - /// Returns the action to dispatch the given message to the handler - /// - [ObsoleteEx(RemoveInVersion = "6.0", TreatAsErrorFromVersion = "5.0", Replacement = "Use the pipeline and replace the InvokeHandlers step")] - public interface IMessageDispatcherFactory - { - /// - /// Returns the action that will dispatch this message - /// - IEnumerable GetDispatcher(Type messageHandlerType, IBuilder builder, object toHandle); - - /// - /// Returns true if the factory is able to dispatch this type - /// - bool CanDispatch(Type handler); - } -} diff --git a/src/NServiceBus.Core/Unicast/IMessageHandlerRegistry.cs b/src/NServiceBus.Core/Unicast/IMessageHandlerRegistry.cs deleted file mode 100644 index 5a57f2e1944..00000000000 --- a/src/NServiceBus.Core/Unicast/IMessageHandlerRegistry.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using System.Collections.Generic; - - /// - /// The registry that keeps track of all known message handlers - /// - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "6.0", - Message = "Not a public API", - Replacement = "MessageHandlerRegistry")] - public interface IMessageHandlerRegistry - { - /// - /// Gets the list of s for the given - /// - IEnumerable GetHandlerTypes(Type messageType); - - /// - /// Lists all message type for which we have handlers - /// - IEnumerable GetMessageTypes(); - - /// - /// Invokes the handle method of the given handler passing the message - /// - /// The handler instance. - /// The message instance. - void InvokeHandle(object handler, object message); - - /// - /// Invokes the timeout method of the given handler passing the message - /// - /// The handler instance. - /// The message instance. - void InvokeTimeout(object handler, object state); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/IncomingMessageOperations.cs b/src/NServiceBus.Core/Unicast/IncomingMessageOperations.cs new file mode 100644 index 00000000000..4b50a71366e --- /dev/null +++ b/src/NServiceBus.Core/Unicast/IncomingMessageOperations.cs @@ -0,0 +1,27 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Transport; + + static class IncomingMessageOperations + { + public static Task ForwardCurrentMessageTo(IIncomingContext context, string destination) + { + var messageBeingProcessed = context.Extensions.Get(); + + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var outgoingMessage = new OutgoingMessage( + messageBeingProcessed.MessageId, + messageBeingProcessed.Headers, + messageBeingProcessed.Body); + + var routingContext = new RoutingContext(outgoingMessage, new UnicastRoutingStrategy(destination), context); + + return pipeline.Invoke(routingContext); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/MessageContext.cs b/src/NServiceBus.Core/Unicast/MessageContext.cs deleted file mode 100644 index d5069e4768e..00000000000 --- a/src/NServiceBus.Core/Unicast/MessageContext.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using System.Collections.Generic; - - /// - /// Implementation of IMessageContext - /// - public class MessageContext : IMessageContext - { - private readonly TransportMessage transportMessage; - - /// - /// Initializes message context from the transport message. - /// - public MessageContext(TransportMessage transportMessage) - { - this.transportMessage = transportMessage; - } - - IDictionary IMessageContext.Headers - { - get { return transportMessage.Headers; } - } - - /// - /// The time at which the incoming message was sent - /// - public DateTime TimeSent - { - get - { - string timeSent; - if (transportMessage.Headers.TryGetValue(Headers.TimeSent, out timeSent)) - { - return DateTimeExtensions.ToUtcDateTime(timeSent); - } - - return DateTime.MinValue; - } - } - - string IMessageContext.Id - { - get { return transportMessage.Id; } - } - - Address IMessageContext.ReplyToAddress - { - get { return transportMessage.ReplyToAddress; } - } - } -} diff --git a/src/NServiceBus.Core/Unicast/MessageEventArgs.cs b/src/NServiceBus.Core/Unicast/MessageEventArgs.cs index a64adb9987d..2d2cf3536e8 100644 --- a/src/NServiceBus.Core/Unicast/MessageEventArgs.cs +++ b/src/NServiceBus.Core/Unicast/MessageEventArgs.cs @@ -20,4 +20,4 @@ public MessageEventArgs(object msg) /// public object Message { get; private set; } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/MessageHandlerRegistry.cs b/src/NServiceBus.Core/Unicast/MessageHandlerRegistry.cs index dfdb21fd415..0fbb09f21b9 100644 --- a/src/NServiceBus.Core/Unicast/MessageHandlerRegistry.cs +++ b/src/NServiceBus.Core/Unicast/MessageHandlerRegistry.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using NServiceBus.Logging; - using NServiceBus.Saga; + using System.Threading.Tasks; + using Logging; + using Pipeline; /// - /// Maintains the message handlers for this endpoint + /// Maintains the message handlers for this endpoint. /// - public class MessageHandlerRegistry : IMessageHandlerRegistry + public partial class MessageHandlerRegistry { internal MessageHandlerRegistry(Conventions conventions) { @@ -18,190 +19,174 @@ internal MessageHandlerRegistry(Conventions conventions) } /// - /// Gets the list of s for the given - /// + /// Gets the list of handlers s for the given + /// . /// - public IEnumerable GetHandlerTypes(Type messageType) + // ReSharper disable once ReturnTypeCanBeEnumerable.Global + public List GetHandlersFor(Type messageType) { + Guard.AgainstNull(nameof(messageType), messageType); if (!conventions.IsMessageType(messageType)) { - return Enumerable.Empty(); + return noMessageHandlers; } - return from keyValue in handlerList - where keyValue.Value.Any(msgTypeHandled => msgTypeHandled.IsAssignableFrom(messageType)) - select keyValue.Key; + var messageHandlers = new List(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var handlersAndMessages in handlerAndMessagesHandledByHandlerCache) + { + var handlerType = handlersAndMessages.Key; + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var messagesBeingHandled in handlersAndMessages.Value) + { + if (messagesBeingHandled.MessageType.IsAssignableFrom(messageType)) + { + messageHandlers.Add(new MessageHandler(messagesBeingHandled.MethodDelegate, handlerType)); + } + } + } + return messageHandlers; } /// - /// Lists all message type for which we have handlers + /// Lists all message type for which we have handlers. /// + /// This method should not be called on a hot path. public IEnumerable GetMessageTypes() { - return from handlers in handlerList.Values - from typeHandled in handlers - where conventions.IsMessageType(typeHandled) - select typeHandled; + return (from messagesBeingHandled in handlerAndMessagesHandledByHandlerCache.Values + from typeHandled in messagesBeingHandled + let messageType = typeHandled.MessageType + where conventions.IsMessageType(messageType) + select messageType).Distinct(); } /// - /// Registers the given message handler type + /// Registers the given potential handler type. /// public void RegisterHandler(Type handlerType) { + Guard.AgainstNull(nameof(handlerType), handlerType); + if (handlerType.IsAbstract) { return; } - var messageTypesThisHandlerHandles = GetMessageTypesIfIsMessageHandler(handlerType).ToList(); + ValidateHandlerType(handlerType); - foreach (var messageType in messageTypesThisHandlerHandles) - { - List typeList; - if (!handlerList.TryGetValue(handlerType, out typeList)) - { - handlerList[handlerType] = typeList = new List(); - } + var messageTypes = GetMessageTypesBeingHandledBy(handlerType); - if (!typeList.Contains(messageType)) + foreach (var messageType in messageTypes) + { + List typeList; + if (!handlerAndMessagesHandledByHandlerCache.TryGetValue(handlerType, out typeList)) { - typeList.Add(messageType); - Log.DebugFormat("Associated '{0}' message with '{1}' handler", messageType, handlerType); + handlerAndMessagesHandledByHandlerCache[handlerType] = typeList = new List(); } - CacheMethodForHandler(handlerType, messageType); + CacheHandlerMethods(handlerType, messageType, typeList); } } /// - /// Invokes the handle method of the given handler passing the message - /// - /// The handler instance. - /// The message instance. - public void InvokeHandle(object handler, object message) - { - Invoke(handler, message, HandlerCache); - } - - /// - /// Invokes the timeout method of the given handler passing the message - /// - /// The handler instance. - /// The message instance. - public void InvokeTimeout(object handler, object state) - { - Invoke(handler, state, TimeoutCache); - } - - /// - /// Registers the method in the cache - /// - /// The object type. - /// the message type. - public void CacheMethodForHandler(Type handler, Type messageType) - { - CacheMethod(handler, messageType, typeof(IHandleMessages<>), HandlerCache); - CacheMethod(handler, messageType, typeof(IHandleTimeouts<>), TimeoutCache); - } - - /// - /// Clears the cache + /// Clears the cache. /// public void Clear() { - HandlerCache.Clear(); - TimeoutCache.Clear(); + handlerAndMessagesHandledByHandlerCache.Clear(); } - static void Invoke(object handler, object message, Dictionary> dictionary) + static void CacheHandlerMethods(Type handler, Type messageType, ICollection typeList) { - List methodList; - if (!dictionary.TryGetValue(handler.GetType().TypeHandle, out methodList)) - { - return; - } - foreach (var delegateHolder in methodList.Where(x => x.MessageType.IsInstanceOfType(message))) - { - delegateHolder.MethodDelegate(handler, message); - } + CacheMethod(handler, messageType, typeof(IHandleMessages<>), typeList); + CacheMethod(handler, messageType, typeof(IHandleTimeouts<>), typeList); } - static void CacheMethod(Type handler, Type messageType, Type interfaceGenericType, Dictionary> cache) + static void CacheMethod(Type handler, Type messageType, Type interfaceGenericType, ICollection methodList) { var handleMethod = GetMethod(handler, messageType, interfaceGenericType); if (handleMethod == null) { return; } + Log.DebugFormat("Associated '{0}' message with '{1}' handler.", messageType, handler); + var delegateHolder = new DelegateHolder { MessageType = messageType, MethodDelegate = handleMethod }; - List methodList; - if (cache.TryGetValue(handler.TypeHandle, out methodList)) - { - if (methodList.Any(x => x.MessageType == messageType)) - { - return; - } - methodList.Add(delegateHolder); - } - else - { - cache[handler.TypeHandle] = new List - { - delegateHolder - }; - } + methodList.Add(delegateHolder); } - static Action GetMethod(Type targetType, Type messageType, Type interfaceGenericType) + static Func GetMethod(Type targetType, Type messageType, Type interfaceGenericType) { var interfaceType = interfaceGenericType.MakeGenericType(messageType); - if (interfaceType.IsAssignableFrom(targetType)) + if (!interfaceType.IsAssignableFrom(targetType)) { - var methodInfo = targetType.GetInterfaceMap(interfaceType).TargetMethods.FirstOrDefault(); - if (methodInfo != null) - { - var target = Expression.Parameter(typeof(object)); - var param = Expression.Parameter(typeof(object)); + return null; + } - var castTarget = Expression.Convert(target, targetType); - var castParam = Expression.Convert(param, methodInfo.GetParameters().First().ParameterType); - var execute = Expression.Call(castTarget, methodInfo, castParam); - return Expression.Lambda>(execute, target, param).Compile(); - } + var methodInfo = targetType.GetInterfaceMap(interfaceType).TargetMethods.FirstOrDefault(); + if (methodInfo == null) + { + return null; } - return null; - } + var target = Expression.Parameter(typeof(object)); + var messageParam = Expression.Parameter(typeof(object)); + var contextParam = Expression.Parameter(typeof(IMessageHandlerContext)); - class DelegateHolder - { - public Type MessageType; - public Action MethodDelegate; - } + var castTarget = Expression.Convert(target, targetType); + + var methodParameters = methodInfo.GetParameters(); + var messageCastParam = Expression.Convert(messageParam, methodParameters.ElementAt(0).ParameterType); - readonly Dictionary> HandlerCache = new Dictionary>(); - readonly Dictionary> TimeoutCache = new Dictionary>(); + Expression body = Expression.Call(castTarget, methodInfo, messageCastParam, contextParam); - IEnumerable GetMessageTypesIfIsMessageHandler(Type type) + return Expression.Lambda>(body, target, messageParam, contextParam).Compile(); + } + + // ReSharper disable once ReturnTypeCanBeEnumerable.Local + static Type[] GetMessageTypesBeingHandledBy(Type type) { - return from t in type.GetInterfaces() + return (from t in type.GetInterfaces() where t.IsGenericType let potentialMessageType = t.GetGenericArguments()[0] where typeof(IHandleMessages<>).MakeGenericType(potentialMessageType).IsAssignableFrom(t) || typeof(IHandleTimeouts<>).MakeGenericType(potentialMessageType).IsAssignableFrom(t) - select potentialMessageType; + select potentialMessageType) + .Distinct() + .ToArray(); } - static ILog Log = LogManager.GetLogger(); + void ValidateHandlerType(Type handlerType) + { + var propertyTypes = handlerType.GetProperties().Select(p => p.PropertyType).ToList(); + var ctorArguments = handlerType.GetConstructors() + .SelectMany(ctor => ctor.GetParameters().Select(p => p.ParameterType)) + .ToList(); + + var dependencies = propertyTypes.Concat(ctorArguments).ToList(); + + if (dependencies.Any(t => typeof(IMessageSession).IsAssignableFrom(t))) + { + throw new Exception($"Interfaces IMessageSession or IEndpointInstance should not be resolved from the container to enable sending or publishing messages from within sagas or message handlers. Instead, use the context parameter on the {handlerType.Name}.Handle method to send or publish messages."); + } + } readonly Conventions conventions; - readonly IDictionary> handlerList = new Dictionary>(); + readonly Dictionary> handlerAndMessagesHandledByHandlerCache = new Dictionary>(); + static List noMessageHandlers = new List(0); + static ILog Log = LogManager.GetLogger(); + + class DelegateHolder + { + public Type MessageType; + public Func MethodDelegate; + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/MessageOperations.cs b/src/NServiceBus.Core/Unicast/MessageOperations.cs new file mode 100644 index 00000000000..5306266a1aa --- /dev/null +++ b/src/NServiceBus.Core/Unicast/MessageOperations.cs @@ -0,0 +1,111 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using MessageInterfaces; + using Pipeline; + + static class MessageOperations + { + public static Task Publish(IBehaviorContext context, Action messageConstructor, PublishOptions options) + { + var mapper = context.Builder.Build(); + return Publish(context, typeof(T), mapper.CreateInstance(messageConstructor), options); + } + + public static Task Publish(IBehaviorContext context, object message, PublishOptions options) + { + return Publish(context, message.GetType(), message, options); + } + + static Task Publish(IBehaviorContext context, Type messageType, object message, PublishOptions options) + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var publishContext = new OutgoingPublishContext( + new OutgoingLogicalMessage(messageType, message), + options, + context); + + return pipeline.Invoke(publishContext); + } + + public static Task Subscribe(IBehaviorContext context, Type eventType, SubscribeOptions options) + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var subscribeContext = new SubscribeContext( + context, + eventType, + options); + + return pipeline.Invoke(subscribeContext); + } + + public static Task Unsubscribe(IBehaviorContext context, Type eventType, UnsubscribeOptions options) + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var subscribeContext = new UnsubscribeContext( + context, + eventType, + options); + + return pipeline.Invoke(subscribeContext); + } + + public static Task Send(IBehaviorContext context, Action messageConstructor, SendOptions options) + { + var mapper = context.Builder.Build(); + + return SendMessage(context, typeof(T), mapper.CreateInstance(messageConstructor), options); + } + + public static Task Send(IBehaviorContext context, object message, SendOptions options) + { + return SendMessage(context, message.GetType(), message, options); + } + + static Task SendMessage(this IBehaviorContext context, Type messageType, object message, SendOptions options) + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var outgoingContext = new OutgoingSendContext( + new OutgoingLogicalMessage(messageType, message), + options, + context); + + return pipeline.Invoke(outgoingContext); + } + + public static Task Reply(IBehaviorContext context, object message, ReplyOptions options) + { + return ReplyMessage(context, message.GetType(), message, options); + } + + public static Task Reply(IBehaviorContext context, Action messageConstructor, ReplyOptions options) + { + var mapper = context.Builder.Build(); + + return ReplyMessage(context, typeof(T), mapper.CreateInstance(messageConstructor), options); + } + + static Task ReplyMessage(this IBehaviorContext context, Type messageType, object message, ReplyOptions options) + { + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var outgoingContext = new OutgoingReplyContext( + new OutgoingLogicalMessage(messageType, message), + options, + context); + + return pipeline.Invoke(outgoingContext); + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/MessageOperationsInvokeHandlerContext.cs b/src/NServiceBus.Core/Unicast/MessageOperationsInvokeHandlerContext.cs new file mode 100644 index 00000000000..9bf2f15da18 --- /dev/null +++ b/src/NServiceBus.Core/Unicast/MessageOperationsInvokeHandlerContext.cs @@ -0,0 +1,34 @@ +namespace NServiceBus +{ + using System.Threading.Tasks; + using Pipeline; + using Routing; + using Settings; + using Transport; + + static class MessageOperationsInvokeHandlerContext + { + public static Task HandleCurrentMessageLater(IInvokeHandlerContext context) + { + if (context.HandleCurrentMessageLaterWasCalled) + { + return TaskEx.CompletedTask; + } + + var messageBeingProcessed = context.Extensions.Get(); + var settings = context.Builder.Build(); + + var cache = context.Extensions.Get(); + var pipeline = cache.Pipeline(); + + var outgoingMessage = new OutgoingMessage( + messageBeingProcessed.MessageId, + messageBeingProcessed.Headers, + messageBeingProcessed.Body); + + var routingContext = new RoutingContext(outgoingMessage, new UnicastRoutingStrategy(settings.LocalAddress()), context); + + return pipeline.Invoke(routingContext); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/DeserializeLogicalMessagesBehavior.cs b/src/NServiceBus.Core/Unicast/Messages/DeserializeLogicalMessagesBehavior.cs deleted file mode 100644 index f27c9860d20..00000000000 --- a/src/NServiceBus.Core/Unicast/Messages/DeserializeLogicalMessagesBehavior.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using Logging; - using NServiceBus.Unicast.Messages; - using NServiceBus.Unicast.Transport; - using Pipeline; - using Pipeline.Contexts; - using Scheduling.Messages; - using Serialization; - using Unicast; - - - class DeserializeLogicalMessagesBehavior : IBehavior - { - public IMessageSerializer MessageSerializer { get; set; } - - public UnicastBus UnicastBus { get; set; } - - public LogicalMessageFactory LogicalMessageFactory { get; set; } - - public MessageMetadataRegistry MessageMetadataRegistry { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var transportMessage = context.PhysicalMessage; - - if (transportMessage.IsControlMessage()) - { - log.Info("Received a control message. Skipping deserialization as control message data is contained in the header."); - next(); - return; - } - try - { - context.LogicalMessages = Extract(transportMessage); - } - catch (Exception exception) - { - throw new MessageDeserializationException(transportMessage.Id, exception); - } - - next(); - } - - List Extract(TransportMessage physicalMessage) - { - if (physicalMessage.Body == null || physicalMessage.Body.Length == 0) - { - return new List(); - } - - string messageTypeIdentifier; - var messageMetadata = new List(); - - if (physicalMessage.Headers.TryGetValue(Headers.EnclosedMessageTypes, out messageTypeIdentifier)) - { - foreach (var messageTypeString in messageTypeIdentifier.Split(';')) - { - var typeString = messageTypeString; - - if (DoesTypeHaveImplAddedByVersion3(typeString)) - { - continue; - } - - if (IsV4OrBelowScheduledTask(typeString)) - { - typeString = typeof(ScheduledTask).AssemblyQualifiedName; - } - - var metadata = MessageMetadataRegistry.GetMessageMetadata(typeString); - if (metadata == null) - { - continue; - } - messageMetadata.Add(metadata); - } - - if (messageMetadata.Count == 0 && physicalMessage.MessageIntent != MessageIntentEnum.Publish) - { - log.WarnFormat("Could not determine message type from message header '{0}'. MessageId: {1}", messageTypeIdentifier, physicalMessage.Id); - } - } - - using (var stream = new MemoryStream(physicalMessage.Body)) - { - var messageTypesToDeserialize = messageMetadata.Select(metadata => metadata.MessageType).ToList(); - return MessageSerializer.Deserialize(stream, messageTypesToDeserialize) - .Select(x => LogicalMessageFactory.Create(x.GetType(), x, physicalMessage.Headers)) - .ToList(); - - } - } - - [ObsoleteEx(RemoveInVersion = "6.0")] - bool DoesTypeHaveImplAddedByVersion3(string existingTypeString) - { - return existingTypeString.Contains("__impl"); - } - - bool IsV4OrBelowScheduledTask(string existingTypeString) - { - return existingTypeString.StartsWith("NServiceBus.Scheduling.Messages.ScheduledTask, NServiceBus.Core"); - } - - static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/ExecuteLogicalMessagesBehavior.cs b/src/NServiceBus.Core/Unicast/Messages/ExecuteLogicalMessagesBehavior.cs deleted file mode 100644 index 385549fc708..00000000000 --- a/src/NServiceBus.Core/Unicast/Messages/ExecuteLogicalMessagesBehavior.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Linq; - using System.Reflection; - using Logging; - using NServiceBus.Unicast.Transport; - using Pipeline; - using Pipeline.Contexts; - - class ExecuteLogicalMessagesBehavior : IBehavior - { - public PipelineExecutor PipelineExecutor { get; set; } - - public void Invoke(IncomingContext context, Action next) - { - var logicalMessages = context.LogicalMessages; - - foreach (var message in logicalMessages) - { - using (context.CreateSnapshotRegion()) - { - context.IncomingLogicalMessage = message; - next(); - } - } - - if (!context.PhysicalMessage.IsControlMessage()) - { - if (!logicalMessages.Any()) - { - log.Warn("Received an empty message - ignoring."); - } - } - } - - static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/LogicalMessage.cs b/src/NServiceBus.Core/Unicast/Messages/LogicalMessage.cs deleted file mode 100644 index 08f2101a0d1..00000000000 --- a/src/NServiceBus.Core/Unicast/Messages/LogicalMessage.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace NServiceBus.Unicast.Messages -{ - using System; - using System.Collections.Generic; - - - /// - /// The logical message. - /// - public class LogicalMessage - { - readonly LogicalMessageFactory factory; - - internal LogicalMessage(Dictionary headers, LogicalMessageFactory factory) - { - this.factory = factory; - Metadata = new MessageMetadata(); - Headers = headers; - } - - internal LogicalMessage(MessageMetadata metadata, object message, Dictionary headers, LogicalMessageFactory factory) - { - this.factory = factory; - Instance = message; - Metadata = metadata; - Headers = headers; - } - - /// - /// Updates the message instance. - /// - /// The new instance. - public void UpdateMessageInstance(object newInstance) - { - var sameInstance = ReferenceEquals(Instance, newInstance); - - Instance = newInstance; - - if (sameInstance) - { - return; - } - - var newLogicalMessage = factory.Create(newInstance); - - Metadata = newLogicalMessage.Metadata; - } - - /// - /// The of the message instance. - /// - public Type MessageType - { - get - { - return Metadata.MessageType; - } - } - - /// - /// Gets other applicative out-of-band information. - /// - public Dictionary Headers { get; private set; } - - /// - /// Message metadata. - /// - public MessageMetadata Metadata { get; private set; } - - /// - /// The message instance. - /// - public object Instance { get; private set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/LogicalMessageFactory.cs b/src/NServiceBus.Core/Unicast/Messages/LogicalMessageFactory.cs deleted file mode 100644 index e07bb6b3d00..00000000000 --- a/src/NServiceBus.Core/Unicast/Messages/LogicalMessageFactory.cs +++ /dev/null @@ -1,114 +0,0 @@ -namespace NServiceBus.Unicast.Messages -{ - using System; - using System.Collections.Generic; - using MessageInterfaces; - using Pipeline; - - - /// - /// Factory to create s. - /// - public class LogicalMessageFactory - { - readonly MessageMetadataRegistry messageMetadataRegistry; - readonly IMessageMapper messageMapper; - readonly PipelineExecutor pipelineExecutor; - - /// - /// Ctor - /// - /// - /// - /// - public LogicalMessageFactory(MessageMetadataRegistry messageMetadataRegistry, IMessageMapper messageMapper, PipelineExecutor pipelineExecutor) - { - this.messageMetadataRegistry = messageMetadataRegistry; - this.messageMapper = messageMapper; - this.pipelineExecutor = pipelineExecutor; - } - - /// - /// Creates a new using the specified message instance. - /// - /// The message instance. - /// A new . - public LogicalMessage Create(object message) - { - if (message == null) - { - throw new ArgumentNullException("message"); - } - - var headers = GetMessageHeaders(message); - - return Create(message.GetType(), message, headers); - } - - /// - /// Creates a new using the specified messageType, message instance and headers. - /// - /// The message type. - /// The message instance. - /// The message headers. - /// A new . - public LogicalMessage Create(Type messageType, object message, Dictionary headers) - { - if (message == null) - { - throw new ArgumentNullException("message"); - } - - if (messageType == null) - { - throw new ArgumentNullException("messageType"); - } - - if (headers == null) - { - throw new ArgumentNullException("headers"); - } - - var realMessageType = messageMapper.GetMappedTypeFor(messageType); - - return new LogicalMessage(messageMetadataRegistry.GetMessageMetadata(realMessageType), message, headers, this); - } - - /// - /// Creates a new control . - /// - /// Any additional headers - public LogicalMessage CreateControl(Dictionary headers) - { - if (headers == null) - { - throw new ArgumentNullException("headers"); - } - - headers.Add(Headers.ControlMessageHeader, true.ToString()); - - return new LogicalMessage(headers, this); - } - - Dictionary GetMessageHeaders(object message) - { - Dictionary> outgoingHeaders; - - if (!pipelineExecutor.CurrentContext.TryGet("NServiceBus.OutgoingHeaders", out outgoingHeaders)) - { - return new Dictionary(); - } - Dictionary outgoingHeadersForThisMessage; - - if (!outgoingHeaders.TryGetValue(message, out outgoingHeadersForThisMessage)) - { - return new Dictionary(); - } - - //remove the entry to allow memory to be reclaimed - outgoingHeaders.Remove(message); - - return outgoingHeadersForThisMessage; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/MessageMetadata.cs b/src/NServiceBus.Core/Unicast/Messages/MessageMetadata.cs index 17e779e3e2b..17f3bea1c08 100644 --- a/src/NServiceBus.Core/Unicast/Messages/MessageMetadata.cs +++ b/src/NServiceBus.Core/Unicast/Messages/MessageMetadata.cs @@ -1,58 +1,42 @@ namespace NServiceBus.Unicast.Messages { using System; - using System.Collections.Generic; - using System.Linq; /// /// Message metadata class. /// - public class MessageMetadata + public partial class MessageMetadata { - readonly Type messageType; - readonly bool recoverable; - readonly IEnumerable messageHierarchy; - readonly TimeSpan timeToBeReceived; + static Type[] emptyHierarchy = new Type[0]; - internal MessageMetadata(Type messageType = null, bool recoverable = false, TimeSpan? timeToBeReceived = null, IEnumerable messageHierarchy = null) + /// + /// Create a new instance of . + /// + /// The type of the message this metadata belongs to. + public MessageMetadata(Type messageType) : this(messageType, null) { - this.messageType = messageType; - this.recoverable = recoverable; - this.messageHierarchy = (messageHierarchy == null ? new List() : new List(messageHierarchy)).AsReadOnly(); - this.timeToBeReceived = timeToBeReceived ?? TimeSpan.MaxValue; } /// - /// The of the message instance. + /// Create a new instance of . /// - public Type MessageType { get { return messageType; } } + /// The type of the message this metadata belongs to. + /// the hierarchy of all message types implemented by the message this metadata belongs to. + public MessageMetadata(Type messageType, Type[] messageHierarchy) + { + MessageType = messageType; + MessageHierarchy = messageHierarchy ?? emptyHierarchy; + } /// - /// Gets whether or not the message is supposed to be guaranteed deliverable. + /// The of the message instance. /// - public bool Recoverable { get { return recoverable; } } + public Type MessageType { get; private set; } - /// - /// Gets the maximum time limit in which the message must be received. - /// - public TimeSpan TimeToBeReceived { get { return timeToBeReceived; } } /// /// The message instance hierarchy. /// - public IEnumerable MessageHierarchy{ get { return messageHierarchy; } } - - /// - /// Returns a string that represents the current object. - /// - /// - /// A string that represents the current object. - /// - /// 2 - public override string ToString() - { - return string.Format("MessageType: {0}, Recoverable: {1}, TimeToBeReceived: {2} , Parent types: {3}", MessageType, Recoverable, - TimeToBeReceived == TimeSpan.MaxValue ? "Not set" : TimeToBeReceived.ToString(), string.Join(";", MessageHierarchy.Select(pt => pt.FullName))); - } + public Type[] MessageHierarchy { get; private set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs b/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs index 71c5d162a7a..1bc9f5d21b9 100644 --- a/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs +++ b/src/NServiceBus.Core/Unicast/Messages/MessageMetadataRegistry.cs @@ -1,100 +1,140 @@ namespace NServiceBus.Unicast.Messages { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Logging; /// - /// Cache of message metadata. + /// Cache of message metadata. /// public class MessageMetadataRegistry { - internal MessageMetadataRegistry(bool defaultToNonPersistentMessages, Conventions conventions) + internal MessageMetadataRegistry(Conventions conventions) { - this.defaultToNonPersistentMessages = defaultToNonPersistentMessages; this.conventions = conventions; } - internal bool DefaultToNonPersistentMessages - { - get { return defaultToNonPersistentMessages; } - set { defaultToNonPersistentMessages = value; } - } - /// - /// Retrieves the for the specified type. + /// Retrieves the for the specified type. /// /// The message type to retrieve metadata for. /// The for the specified type. public MessageMetadata GetMessageMetadata(Type messageType) { + Guard.AgainstNull(nameof(messageType), messageType); MessageMetadata metadata; - if (messages.TryGetValue(messageType, out metadata)) + if (messages.TryGetValue(messageType.TypeHandle, out metadata)) { return metadata; } - var message = string.Format("Could not find metadata for '{0}'.{1}Please ensure the following:{1}1. '{0}' is included in initial scanning. {1}2. '{0}' implements either 'IMessage', 'IEvent' or 'ICommand' or alternatively, if you don't want to implement an interface, you can use 'Unobtrusive Mode'.", messageType.FullName, Environment.NewLine); + + metadata = RegisterMessageType(messageType); + if (metadata != null) + { + return metadata; + } + + var message = $"Could not find metadata for '{messageType.FullName}'.{Environment.NewLine}Ensure the following:{Environment.NewLine}1. '{messageType.FullName}' is included in initial scanning. {Environment.NewLine}2. '{messageType.FullName}' implements either 'IMessage', 'IEvent' or 'ICommand' or alternatively, if you don't want to implement an interface, you can use 'Unobtrusive Mode'."; throw new Exception(message); } /// - /// Retrieves the for the message identifier. + /// Retrieves the for the message identifier. /// /// The message identifier to retrieve metadata for. /// The for the specified type. public MessageMetadata GetMessageMetadata(string messageTypeIdentifier) { - if (string.IsNullOrEmpty(messageTypeIdentifier)) - { - throw new ArgumentException("MessageTypeIdentifier passed is null or empty"); - } + Guard.AgainstNullAndEmpty(nameof(messageTypeIdentifier), messageTypeIdentifier); - var messageType = Type.GetType(messageTypeIdentifier, false); + var messageType = GetType(messageTypeIdentifier); if (messageType == null) { Logger.DebugFormat("Message type: '{0}' could not be determined by a 'Type.GetType', scanning known messages for a match", messageTypeIdentifier); - return messages.Values.FirstOrDefault(m => m.MessageType.FullName == messageTypeIdentifier); + + foreach (var item in messages.Values) + { + if (item.MessageType.FullName == messageTypeIdentifier) + { + cachedTypes[messageTypeIdentifier] = item.MessageType; + return item; + } + } + + return null; } + MessageMetadata metadata; - if (messages.TryGetValue(messageType, out metadata)) + if (messages.TryGetValue(messageType.TypeHandle, out metadata)) { return metadata; } - Logger.WarnFormat("Message header '{0}' was mapped to type '{1}' but that type was not found in the message registry, please make sure the same message registration conventions are used in all endpoints, specially if you are using unobtrusive mode. ", messageType, messageType.FullName); + + metadata = RegisterMessageType(messageType); + if (metadata != null) + { + return metadata; + } + + Logger.WarnFormat("Message header '{0}' was mapped to type '{1}' but that type was not found in the message registry, ensure the same message registration conventions are used in all endpoints, especially if using unobtrusive mode. ", messageType, messageType.FullName); return null; } + Type GetType(string messageTypeIdentifier) + { + Type type; + + if (!cachedTypes.TryGetValue(messageTypeIdentifier, out type)) + { + type = Type.GetType(messageTypeIdentifier, false); + cachedTypes[messageTypeIdentifier] = type; + } + + return type; + } + internal IEnumerable GetAllMessages() { return new List(messages.Values); } - internal void RegisterMessageType(Type messageType) + internal void RegisterMessageTypesFoundIn(IList availableTypes) + { + foreach (var type in availableTypes) + { + RegisterMessageType(type); + } + } + + MessageMetadata RegisterMessageType(Type messageType) { - //get the parent types - var parentMessages = GetParentTypes(messageType) - .Where(conventions.IsMessageType) - .OrderByDescending(PlaceInMessageHierarchy) - .ToList(); - - var timeToBeReceived = conventions.GetTimeToBeReceived(messageType); - var isExpressMessageType = conventions.IsExpressMessageType(messageType); - var recoverable = !isExpressMessageType && !defaultToNonPersistentMessages; - var metadata = new MessageMetadata(messageType, recoverable, timeToBeReceived, new[] + if (conventions.IsMessageType(messageType)) { - messageType - }.Concat(parentMessages)); + //get the parent types + var parentMessages = GetParentTypes(messageType) + .Where(conventions.IsMessageType) + .OrderByDescending(PlaceInMessageHierarchy); + + var metadata = new MessageMetadata(messageType, new[] + { + messageType + }.Concat(parentMessages).ToArray()); + + messages[messageType.TypeHandle] = metadata; - messages[messageType] = metadata; + return metadata; + } + return null; } - int PlaceInMessageHierarchy(Type type) + static int PlaceInMessageHierarchy(Type type) { if (type.IsInterface) { - return type.GetInterfaces().Count(); + return type.GetInterfaces().Length; } var result = 0; @@ -117,7 +157,7 @@ static IEnumerable GetParentTypes(Type type) // return all inherited types var currentBaseType = type.BaseType; - var objectType = typeof(Object); + var objectType = typeof(object); while (currentBaseType != null && currentBaseType != objectType) { yield return currentBaseType; @@ -125,9 +165,10 @@ static IEnumerable GetParentTypes(Type type) } } + Conventions conventions; + ConcurrentDictionary messages = new ConcurrentDictionary(); + ConcurrentDictionary cachedTypes = new ConcurrentDictionary(); + static ILog Logger = LogManager.GetLogger(); - readonly Conventions conventions; - readonly Dictionary messages = new Dictionary(); - bool defaultToNonPersistentMessages; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/MessagingBestPractices.cs b/src/NServiceBus.Core/Unicast/MessagingBestPractices.cs deleted file mode 100644 index 12c06bd9e76..00000000000 --- a/src/NServiceBus.Core/Unicast/MessagingBestPractices.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using Logging; - - class MessagingBestPractices - { - public static void AssertIsValidForSend(Type messageType, Conventions conventions) - { - if (conventions.IsEventType(messageType)) - { - throw new InvalidOperationException("Events can have multiple recipient so they should be published"); - } - } - - public static void AssertIsValidForReply(Type messageType, Conventions conventions) - { - if (conventions.IsCommandType(messageType) || conventions.IsEventType(messageType)) - { - throw new InvalidOperationException("Reply is neither supported for Commands nor Events. Commands should be sent to their logical owner using bus.Send and bus. Events should be Published with bus.Publish."); - } - } - - public static void AssertIsValidForPubSub(Type messageType, Conventions conventions) - { - if (conventions.IsCommandType(messageType)) - { - throw new InvalidOperationException("Pub/Sub is not supported for Commands. They should be be sent direct to their logical owner."); - } - - if (!conventions.IsEventType(messageType)) - { - Log.Info("You are using a basic message to do pub/sub, consider implementing the more specific ICommand and IEvent interfaces to help NServiceBus to enforce messaging best practices for you."); - } - } - - static ILog Log = LogManager.GetLogger(); - } -} diff --git a/src/NServiceBus.Core/Unicast/PublishOptions.cs b/src/NServiceBus.Core/Unicast/PublishOptions.cs deleted file mode 100644 index 6047abd2e24..00000000000 --- a/src/NServiceBus.Core/Unicast/PublishOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - - /// - /// Additional options that only applies when publishing messages - /// - public class PublishOptions : DeliveryOptions - { - /// - /// The type of event to publish - /// - public Type EventType { get; private set; } - - /// - /// The event type is required for a publish - /// - public PublishOptions(Type eventType) - { - EventType = eventType; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Publishing/StorageDrivenPublisher.cs b/src/NServiceBus.Core/Unicast/Publishing/StorageDrivenPublisher.cs deleted file mode 100644 index 61ab644db9d..00000000000 --- a/src/NServiceBus.Core/Unicast/Publishing/StorageDrivenPublisher.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace NServiceBus.Unicast.Publishing -{ - using System; - using System.Linq; - using Messages; - using Pipeline; - using Subscriptions; - using Subscriptions.MessageDrivenSubscriptions; - using Transports; - - class StorageDrivenPublisher:IPublishMessages - { - public ISubscriptionStorage SubscriptionStorage { get; set; } - - public ISendMessages MessageSender{ get; set; } - - public PipelineExecutor PipelineExecutor { get; set; } - - public MessageMetadataRegistry MessageMetadataRegistry { get; set; } - - public void Publish(TransportMessage message, PublishOptions publishOptions) - { - if (SubscriptionStorage == null) - { - throw new InvalidOperationException("Cannot publish on this endpoint - no subscription storage has been configured."); - } - - var eventTypesToPublish = MessageMetadataRegistry.GetMessageMetadata(publishOptions.EventType.FullName) - .MessageHierarchy - .Distinct() - .ToList(); - - var subscribers = SubscriptionStorage.GetSubscriberAddressesForMessage(eventTypesToPublish.Select(t => new MessageType(t))).ToList(); - - if (!subscribers.Any()) - { - PipelineExecutor.CurrentContext.Set("NoSubscribersFoundForMessage",true); - return; - } - - PipelineExecutor.CurrentContext.Set("SubscribersForEvent", subscribers); - - foreach (var subscriber in subscribers) - { - //this is unicast so we give the message a unique ID - message.ChangeMessageId(CombGuid.Generate().ToString()); - - MessageSender.Send(message, new SendOptions(subscriber) - { - ReplyToAddress = publishOptions.ReplyToAddress, - EnforceMessagingBestPractices = publishOptions.EnforceMessagingBestPractices, - EnlistInReceiveTransaction = publishOptions.EnlistInReceiveTransaction, - }); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Queuing/IWantQueueCreated.cs b/src/NServiceBus.Core/Unicast/Queuing/IWantQueueCreated.cs deleted file mode 100644 index f1a4f91b3ee..00000000000 --- a/src/NServiceBus.Core/Unicast/Queuing/IWantQueueCreated.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NServiceBus.Unicast.Queuing -{ - /// - /// Implementers signal their wish to create a queue, regardless of technology (e.g. MSMQ or SQL Server). - /// - public interface IWantQueueCreated - { - /// - /// Address of queue the implementer requires. - /// - Address Address { get; } - - /// - /// True if no need to create queue - /// - bool ShouldCreateQueue(); - } -} diff --git a/src/NServiceBus.Core/Unicast/Queuing/Installers/AuditQueueCreator.cs b/src/NServiceBus.Core/Unicast/Queuing/Installers/AuditQueueCreator.cs deleted file mode 100644 index f2acbc4569b..00000000000 --- a/src/NServiceBus.Core/Unicast/Queuing/Installers/AuditQueueCreator.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Unicast.Queuing.Installers -{ - class AuditQueueCreator : IWantQueueCreated - { - public Address AuditQueue { get; set; } - - public Address Address - { - get { return AuditQueue; } - } - - public bool Enabled { get; set; } - - public bool ShouldCreateQueue() - { - return Enabled; - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Queuing/Installers/EndpointInputQueueCreator.cs b/src/NServiceBus.Core/Unicast/Queuing/Installers/EndpointInputQueueCreator.cs deleted file mode 100644 index eaf8764b5ea..00000000000 --- a/src/NServiceBus.Core/Unicast/Queuing/Installers/EndpointInputQueueCreator.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus.Unicast.Queuing.Installers -{ - class EndpointInputQueueCreator : IWantQueueCreated - { - Address address; - - public EndpointInputQueueCreator(Configure config) - { - address = config.LocalAddress; - } - - /// - /// Endpoint input name - /// - public Address Address - { - get { return address; } - } - - public bool ShouldCreateQueue() - { - return true; - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Queuing/Installers/ForwardReceivedMessagesToQueueCreator.cs b/src/NServiceBus.Core/Unicast/Queuing/Installers/ForwardReceivedMessagesToQueueCreator.cs deleted file mode 100644 index 43d53740b61..00000000000 --- a/src/NServiceBus.Core/Unicast/Queuing/Installers/ForwardReceivedMessagesToQueueCreator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus.Unicast.Queuing.Installers -{ - - class ForwardReceivedMessagesToQueueCreator : IWantQueueCreated - { - public Address Address{get; set; } - public bool Enabled { get; set; } - - public bool ShouldCreateQueue() - { - return Enabled; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Queuing/QueueNotFoundException.cs b/src/NServiceBus.Core/Unicast/Queuing/QueueNotFoundException.cs index b0ab3cfbc90..4bde9d229f2 100644 --- a/src/NServiceBus.Core/Unicast/Queuing/QueueNotFoundException.cs +++ b/src/NServiceBus.Core/Unicast/Queuing/QueueNotFoundException.cs @@ -1,61 +1,64 @@ namespace NServiceBus.Unicast.Queuing { using System; + using System.Runtime.Serialization; /// - /// Thrown when the queue could not be found + /// Thrown when the queue could not be found. /// [Serializable] public class QueueNotFoundException : Exception { /// - /// The queue address + /// Initializes a new instance of . /// - public Address Queue { get; set; } + public QueueNotFoundException() + { + } /// - /// Ctor + /// Initializes a new instance of . /// - public QueueNotFoundException() + [ObsoleteEx( + ReplacementTypeOrMember = "QueueNotFoundException(string queue, string message, Exception inner)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + // ReSharper disable UnusedParameter.Local + public QueueNotFoundException(Address queue, string message, Exception inner) + // ReSharper restore UnusedParameter.Local { + throw new NotImplementedException(); } /// - /// Ctor + /// Initializes a new instance of . /// - /// - /// - /// - public QueueNotFoundException(Address queue, string message, Exception inner) : base( message, inner ) + public QueueNotFoundException(string queue, string message, Exception inner) : base(message, inner) { Queue = queue; } /// - /// Ctor + /// Initializes a new instance of . /// - /// - /// - protected QueueNotFoundException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) + protected QueueNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { - if (info != null) - Queue = Address.Parse(info.GetString("Queue")); + Queue = info.GetString("Queue"); } /// - /// Gets the object data for serialization purposes + /// The queue address. + /// + public string Queue { get; set; } + + /// + /// Gets the object data for serialization purposes. /// - /// - /// - public override void GetObjectData( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) + public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); - info.AddValue("Queue", Queue.ToString()); + info.AddValue("Queue", Queue); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Queuing/QueuesCreator.cs b/src/NServiceBus.Core/Unicast/Queuing/QueuesCreator.cs index fd75a60d3cb..b7fb907d1cf 100644 --- a/src/NServiceBus.Core/Unicast/Queuing/QueuesCreator.cs +++ b/src/NServiceBus.Core/Unicast/Queuing/QueuesCreator.cs @@ -1,49 +1,36 @@ - -namespace NServiceBus.Unicast.Queuing +namespace NServiceBus { - using System; - using System.Linq; + using System.Threading.Tasks; using Installation; - using Logging; - using NServiceBus.Configuration.AdvanceExtensibility; - using Transports; + using ObjectBuilder; + using Settings; + using Transport; - class QueuesCreator : INeedInitialization, INeedToInstallSomething + class QueuesCreator : INeedToInstallSomething { - public ICreateQueues QueueCreator { get; set; } + public QueuesCreator(IBuilder builder, ReadOnlySettings settings) + { + this.builder = builder; + this.settings = settings; + } - public void Install(string identity, Configure config) + public Task Install(string identity) { - if (config.Settings.Get("Endpoint.SendOnly")) + if (settings.Get("Endpoint.SendOnly")) { - return; + return TaskEx.CompletedTask; } - - if (!config.CreateQueues()) + if (!settings.CreateQueues()) { - return; + return TaskEx.CompletedTask; } + var queueCreator = builder.Build(); + var queueBindings = settings.Get(); - var wantQueueCreatedInstances = config.Builder.BuildAll().ToList(); - - foreach (var wantQueueCreatedInstance in wantQueueCreatedInstances.Where(wantQueueCreatedInstance => wantQueueCreatedInstance.ShouldCreateQueue())) - { - if (wantQueueCreatedInstance.Address == null) - { - throw new InvalidOperationException(string.Format("IWantQueueCreated implementation {0} returned a null address", wantQueueCreatedInstance.GetType().FullName)); - } - - QueueCreator.CreateQueueIfNecessary(wantQueueCreatedInstance.Address, identity); - Logger.DebugFormat("Verified that the queue: [{0}] existed", wantQueueCreatedInstance.Address); - } - } - - public void Customize(BusConfiguration configuration) - { - Configure.ForAllTypes(configuration.GetSettings().GetAvailableTypes(), - type => configuration.RegisterComponents(c => c.ConfigureComponent(type, DependencyLifecycle.InstancePerCall))); + return queueCreator.CreateQueueIfNecessary(queueBindings, identity); } - static ILog Logger = LogManager.GetLogger(); + readonly IBuilder builder; + readonly ReadOnlySettings settings; } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/ReplyOptions.cs b/src/NServiceBus.Core/Unicast/ReplyOptions.cs deleted file mode 100644 index 5d47684cd6b..00000000000 --- a/src/NServiceBus.Core/Unicast/ReplyOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - - /// - /// Additional options that only apply for reply messages - /// - public class ReplyOptions : SendOptions - { - /// - /// Both a destination and a correlation id is required when replying - /// - public ReplyOptions(Address destination, string correlationId):base(destination) - { - if (destination == null) - { - throw new InvalidOperationException("Can't reply with null reply-to-address field. It can happen if you are using a SendOnly client."); - } - - CorrelationId = correlationId; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Routing/StaticMessageRouter.cs b/src/NServiceBus.Core/Unicast/Routing/StaticMessageRouter.cs deleted file mode 100644 index 9fc2114c1eb..00000000000 --- a/src/NServiceBus.Core/Unicast/Routing/StaticMessageRouter.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace NServiceBus.Unicast.Routing -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using Logging; - - /// - /// The default message router - /// - public class StaticMessageRouter - { - /// - /// Initializes the router with all known messages - /// - public StaticMessageRouter(IEnumerable knownMessages) - { - routes = new ConcurrentDictionary>(); - foreach (var knownMessage in knownMessages) - { - routes[knownMessage] = new List
(); - } - } - - /// - /// Set to true if the router should autosubscribe messages not defined as events - /// - public bool SubscribeToPlainMessages { get; set; } - - /// - /// Returns all the routes for a given message - /// - /// The of the message to get the destination list for. - public List
GetDestinationFor(Type messageType) - { - List
address; - if (!routes.TryGetValue(messageType, out address)) - { - return new List
(); - } - - return address; - } - - /// - /// Registers a route for the given event - /// - /// The of the event - /// The representing the logical owner for the event - public void RegisterEventRoute(Type eventType, Address endpointAddress) - { - if (endpointAddress == null || endpointAddress == Address.Undefined) - { - throw new InvalidOperationException(String.Format("'{0}' can't be registered with Address.Undefined route.", eventType.FullName)); - } - - List
currentAddress; - - if (!routes.TryGetValue(eventType, out currentAddress)) - { - routes[eventType] = currentAddress = new List
(); - } - - Logger.DebugFormat(currentAddress.Any() ? "Routing for message: {0} appending {1}" : "Routing for message: {0} set to {1}", eventType, endpointAddress); - - currentAddress.Add(endpointAddress); - - foreach (var route in routes.Where(route => eventType != route.Key && route.Key.IsAssignableFrom(eventType))) - { - if (route.Value.Any()) - { - Logger.InfoFormat("Routing for inherited message: {0}({1}) appending {2}", route.Key, eventType, endpointAddress); - } - else - { - Logger.DebugFormat("Routing for inherited message: {0}({1}) set to {2}", route.Key, eventType, endpointAddress); - } - - route.Value.Add(endpointAddress); - } - } - - /// - /// Registers a route for the given message type - /// - /// The message type - /// The address of the logical owner - public void RegisterMessageRoute(Type messageType, Address endpointAddress) - { - if (endpointAddress == null || endpointAddress == Address.Undefined) - { - throw new InvalidOperationException(String.Format("'{0}' can't be registered with Address.Undefined route.", messageType.FullName)); - } - - List
currentAddress; - - if (!routes.TryGetValue(messageType, out currentAddress)) - { - routes[messageType] = currentAddress = new List
(); - } - - Logger.DebugFormat("Routing for message: {0} set to {1}", messageType, endpointAddress); - currentAddress.Clear(); - currentAddress.Add(endpointAddress); - - //go through the existing routes and see if this means that we can route any of those - foreach (var route in routes.Where(route => messageType != route.Key && route.Key.IsAssignableFrom(messageType))) - { - Logger.DebugFormat("Routing for inherited message: {0}({1}) set to {2}", route.Key, messageType, endpointAddress); - route.Value.Clear(); - route.Value.Add(endpointAddress); - } - } - - static ILog Logger = LogManager.GetLogger(); - readonly ConcurrentDictionary> routes; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/RunningEndpointInstance.cs b/src/NServiceBus.Core/Unicast/RunningEndpointInstance.cs new file mode 100644 index 00000000000..114170394f5 --- /dev/null +++ b/src/NServiceBus.Core/Unicast/RunningEndpointInstance.cs @@ -0,0 +1,115 @@ +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Features; + using Logging; + using ObjectBuilder; + using Settings; + using Transport; + using UnicastBus = Unicast.UnicastBus; + + class RunningEndpointInstance : IEndpointInstance + { + public RunningEndpointInstance(SettingsHolder settings, IBuilder builder, List receivers, FeatureRunner featureRunner, IMessageSession messageSession, TransportInfrastructure transportInfrastructure) + { + this.settings = settings; + this.builder = builder; + this.receivers = receivers; + this.featureRunner = featureRunner; + this.messageSession = messageSession; + this.transportInfrastructure = transportInfrastructure; + } + + public async Task Stop() + { + if (stopped) + { + return; + } + + await stopSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + if (stopped) + { + return; + } + + Log.Info("Initiating shutdown."); + + await StopReceivers().ConfigureAwait(false); + await featureRunner.Stop(messageSession).ConfigureAwait(false); + await transportInfrastructure.Stop().ConfigureAwait(false); + settings.Clear(); + builder.Dispose(); + + stopped = true; + Log.Info("Shutdown complete."); + } + finally + { + stopSemaphore.Release(); + } + } + + public Task Send(object message, SendOptions options) + { + return messageSession.Send(message, options); + } + + public Task Send(Action messageConstructor, SendOptions options) + { + return messageSession.Send(messageConstructor, options); + } + + public Task Publish(object message, PublishOptions options) + { + return messageSession.Publish(message, options); + } + + public Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + return messageSession.Publish(messageConstructor, publishOptions); + } + + public Task Subscribe(Type eventType, SubscribeOptions options) + { + return messageSession.Subscribe(eventType, options); + } + + public Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + return messageSession.Unsubscribe(eventType, options); + } + + Task StopReceivers() + { + var receiverStopTasks = receivers.Select(async receiver => + { + Log.DebugFormat("Stopping {0} receiver", receiver.Id); + await receiver.Stop().ConfigureAwait(false); + Log.DebugFormat("Stopped {0} receiver", receiver.Id); + }); + + return Task.WhenAll(receiverStopTasks); + } + + IBuilder builder; + List receivers; + FeatureRunner featureRunner; + IMessageSession messageSession; + TransportInfrastructure transportInfrastructure; + + SettingsHolder settings; + + volatile bool stopped; + SemaphoreSlim stopSemaphore = new SemaphoreSlim(1); + + static ILog Log = LogManager.GetLogger(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/SendOptions.cs b/src/NServiceBus.Core/Unicast/SendOptions.cs deleted file mode 100644 index d0af47cd656..00000000000 --- a/src/NServiceBus.Core/Unicast/SendOptions.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - - /// - /// Controls how a message will be sent by the transport - /// - public class SendOptions : DeliveryOptions - { - TimeSpan? delayDeliveryWith; - - /// - /// Creates an instance of . - /// - /// Address where to send this message - public SendOptions(Address destination) - { - Destination = destination; - } - - /// - /// Creates an instance of . - /// - /// Address where to send this message - public SendOptions(string destination) - : this(Address.Parse(destination)) - { - } - - /// - /// The correlation id to be used on the message. Mostly used when doing Bus.Reply - /// - public string CorrelationId { get; set; } - - /// - /// The time when the message should be delivered to the destination - /// - public DateTime? DeliverAt { get; set; } - - - /// - /// How long to delay delivery of the message - /// - public TimeSpan? DelayDeliveryWith - { - get { return delayDeliveryWith; } - set - { - if (value < TimeSpan.Zero) - { - throw new Exception("timespan cannot be less than zero"); - } - delayDeliveryWith = value; - } - } - - /// - /// Address where to send this message - /// - public Address Destination { get; set; } - - /// - /// The TTR to use for this message - /// - public TimeSpan? TimeToBeReceived { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/ISubscriptionStorage.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/ISubscriptionStorage.cs deleted file mode 100644 index c50ace20610..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/ISubscriptionStorage.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions -{ - using System.Collections.Generic; - - /// - /// Defines storage for subscriptions - /// - public interface ISubscriptionStorage - { - - /// - /// Subscribes the given client address to messages of the given types. - /// - void Subscribe(Address client, IEnumerable messageTypes); - - /// - /// Unsubscribes the given client address from messages of the given types. - /// - void Unsubscribe(Address client, IEnumerable messageTypes); - - /// - /// Returns a list of addresses of subscribers that previously requested to be notified - /// of messages of the given message types. - /// - IEnumerable
GetSubscriberAddressesForMessage(IEnumerable messageTypes); - - /// - /// Notifies the subscription storage that now is the time to perform - /// any initialization work - /// - void Init(); - } -} diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs deleted file mode 100644 index 4b1143c3c66..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageDrivenSubscriptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Features -{ - using Unicast.Subscriptions.MessageDrivenSubscriptions; - - /// - /// Used to configure Message Driven Subscriptions - /// - public class MessageDrivenSubscriptions : Feature - { - - internal MessageDrivenSubscriptions() - { - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.SingleInstance); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageType.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageType.cs deleted file mode 100644 index e91bbc221cc..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/MessageType.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions -{ - using System; - using System.Linq; - - /// - /// Representation of a message type that clients can be subscribed to - /// - public class MessageType - { - /// - /// Initializes the message type from the given type - /// - public MessageType(Type type) - { - Version = type.Assembly.GetName().Version; - TypeName = type.FullName; - } - - /// - /// Initializes the message type from the given string. - /// - public MessageType(string messageTypeString) - { - var parts = messageTypeString.Split(','); - - - Version = ParseVersion(messageTypeString); - TypeName = parts.First(); - } - - /// - /// Initializes the message type from the given string. - /// - public MessageType(string typeName, string versionString) - { - Version = ParseVersion(versionString); - TypeName = typeName; - } - - /// - /// Initializes the message type from the given string. - /// - public MessageType(string typeName, Version version) - { - Version = version; - TypeName = typeName; - } - - Version ParseVersion(string versionString) - { - const string version = "Version="; - var index = versionString.IndexOf(version); - - if (index >= 0) - versionString = versionString.Substring(index + version.Length) - .Split(',').First(); - return Version.Parse(versionString); - } - - - /// - /// TypeName of the message - /// - public string TypeName { get; private set; } - - /// - /// Version of the message - /// - public Version Version { get; private set; } - - /// - /// Overridden to append Version along with Type Name - /// - public override string ToString() - { - return TypeName + ", Version=" + Version; - } - - /// - /// Equality, only major version is used - /// - public bool Equals(MessageType other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.TypeName, TypeName); - } - - /// - /// Equality, only Type is same - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(MessageType)) return false; - return Equals((MessageType)obj); - } - - /// - /// Gets Hash Code - /// - public override int GetHashCode() - { - return TypeName.GetHashCode(); - } - - /// - /// Equality - /// - public static bool operator ==(MessageType left, MessageType right) - { - return Equals(left, right); - } - - /// - /// Equality - /// - public static bool operator !=(MessageType left, MessageType right) - { - return !Equals(left, right); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/NoopSubscriptionAuthorizer.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/NoopSubscriptionAuthorizer.cs deleted file mode 100644 index 32eb6729811..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/NoopSubscriptionAuthorizer.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions -{ - using System.Collections.Generic; - - class NoopSubscriptionAuthorizer : IAuthorizeSubscriptions - { - public bool AuthorizeSubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - return true; - } - - public bool AuthorizeUnsubscribe(string messageType, string clientEndpoint, IDictionary headers) - { - return true; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageDrivenPublishing.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageDrivenPublishing.cs deleted file mode 100644 index 63251c792ae..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageDrivenPublishing.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Features -{ - /// - /// Adds support for pub/sub using a external subscription storage. This brings pub/sub to transport that lacks native support. - /// - public class StorageDrivenPublishing : Feature - { - internal StorageDrivenPublishing() - { - } - - /// - /// See - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - context.Container.ConfigureComponent(DependencyLifecycle.InstancePerCall); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageInitializer.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageInitializer.cs deleted file mode 100644 index 6b447f11682..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/StorageInitializer.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions -{ - internal class StorageInitializer : IWantToRunWhenBusStartsAndStops - { - public ISubscriptionStorage SubscriptionStorage { get; set; } - - public void Start() - { - if (SubscriptionStorage != null) - { - SubscriptionStorage.Init(); - } - } - - public void Stop() - { - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionManager.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionManager.cs deleted file mode 100644 index 1c4532f5fda..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionManager.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions -{ - using System; - using System.Threading; - using Logging; - using Queuing; - using Transport; - using Transports; - - class SubscriptionManager : IManageSubscriptions - { - public ISendMessages MessageSender { get; set; } - public ISubscriptionStorage SubscriptionStorage { get; set; } - - public Configure Configure { get; set; } - - public void Subscribe(Type eventType, Address publisherAddress) - { - if (publisherAddress == Address.Undefined) - { - throw new InvalidOperationException(string.Format("No destination could be found for message type {0}. Check the section of the configuration of this endpoint for an entry either for this specific message type or for its assembly.", eventType)); - } - - Logger.Info("Subscribing to " + eventType.AssemblyQualifiedName + " at publisher queue " + publisherAddress); - - var subscriptionMessage = CreateControlMessage(eventType); - subscriptionMessage.MessageIntent = MessageIntentEnum.Subscribe; - - ThreadPool.QueueUserWorkItem(state => - SendSubscribeMessageWithRetries(publisherAddress, subscriptionMessage, eventType.AssemblyQualifiedName)); - } - - public void Unsubscribe(Type eventType, Address publisherAddress) - { - if (publisherAddress == Address.Undefined) - { - throw new InvalidOperationException(string.Format("No destination could be found for message type {0}. Check the section of the configuration of this endpoint for an entry either for this specific message type or for its assembly.", eventType)); - } - - Logger.Info("Unsubscribing from " + eventType.AssemblyQualifiedName + " at publisher queue " + publisherAddress); - - var subscriptionMessage = CreateControlMessage(eventType); - subscriptionMessage.MessageIntent = MessageIntentEnum.Unsubscribe; - - MessageSender.Send(subscriptionMessage, new SendOptions(publisherAddress) - { - ReplyToAddress = Configure.PublicReturnAddress - }); - } - - static TransportMessage CreateControlMessage(Type eventType) - { - var subscriptionMessage = ControlMessage.Create(); - - subscriptionMessage.Headers[Headers.SubscriptionMessageType] = eventType.AssemblyQualifiedName; - return subscriptionMessage; - } - - void SendSubscribeMessageWithRetries(Address destination, TransportMessage subscriptionMessage, string messageType, int retriesCount = 0) - { - try - { - MessageSender.Send(subscriptionMessage, new SendOptions(destination) - { - ReplyToAddress = Configure.PublicReturnAddress - }); - } - catch (QueueNotFoundException ex) - { - if (retriesCount < 10) - { - Thread.Sleep(TimeSpan.FromSeconds(2)); - SendSubscribeMessageWithRetries(destination, subscriptionMessage, messageType, ++retriesCount); - } - else - { - Logger.ErrorFormat("Failed to subscribe to {0} at publisher queue {1}", ex, messageType, destination); - } - } - } - - static ILog Logger = LogManager.GetLogger(); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs b/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs deleted file mode 100644 index fb5a22948d4..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/MessageDrivenSubscriptions/SubscriptionReceiverBehavior.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace NServiceBus -{ - using System; - using System.Diagnostics; - using System.Linq; - using Logging; - using NServiceBus.Unicast.Subscriptions; - using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; - using Pipeline; - using Pipeline.Contexts; - - class SubscriptionReceiverBehavior : IBehavior - { - public ISubscriptionStorage SubscriptionStorage { get; set; } - - public IAuthorizeSubscriptions SubscriptionAuthorizer - { - get { return subscriptionAuthorizer ?? (subscriptionAuthorizer = new NoopSubscriptionAuthorizer()); } - set { subscriptionAuthorizer = value; } - } - - public void Invoke(IncomingContext context, Action next) - { - var transportMessage = context.PhysicalMessage; - var messageTypeString = GetSubscriptionMessageTypeFrom(transportMessage); - - var intent = transportMessage.MessageIntent; - - if (string.IsNullOrEmpty(messageTypeString) && intent != MessageIntentEnum.Subscribe && intent != MessageIntentEnum.Unsubscribe) - { - next(); - return; - } - - if (string.IsNullOrEmpty(messageTypeString)) - { - throw new InvalidOperationException("Message intent is Subscribe, but the subscription message type header is missing!"); - } - - if (intent != MessageIntentEnum.Subscribe && intent != MessageIntentEnum.Unsubscribe) - { - throw new InvalidOperationException("Subscription messages need to have intent set to Subscribe/Unsubscribe"); - } - - var subscriberAddress = transportMessage.ReplyToAddress; - - if (subscriberAddress == null || subscriberAddress == Address.Undefined) - { - throw new InvalidOperationException("Subscription message arrived without a valid ReplyToAddress"); - } - - - if (SubscriptionStorage == null) - { - var warning = string.Format("Subscription message from {0} arrived at this endpoint, yet this endpoint is not configured to be a publisher. To avoid this warning make this endpoint a publisher by configuring a subscription storage or using the AsA_Publisher role.", subscriberAddress); - Logger.WarnFormat(warning); - - if (Debugger.IsAttached) // only under debug, so that we don't expose ourselves to a denial of service - { - throw new InvalidOperationException(warning); // and cause message to go to error queue by throwing an exception - } - - return; - } - - if (transportMessage.MessageIntent == MessageIntentEnum.Subscribe) - { - if (!SubscriptionAuthorizer.AuthorizeSubscribe(messageTypeString, subscriberAddress.ToString(), transportMessage.Headers)) - { - Logger.Debug(string.Format("Subscription request from {0} on message type {1} was refused.", subscriberAddress, messageTypeString)); - } - else - { - Logger.Info("Subscribing " + subscriberAddress + " to message type " + messageTypeString); - - var mt = new MessageType(messageTypeString); - - SubscriptionStorage.Subscribe(transportMessage.ReplyToAddress, new[] - { - mt - }); - } - - return; - } - - - if (!SubscriptionAuthorizer.AuthorizeUnsubscribe(messageTypeString, subscriberAddress.ToString(), transportMessage.Headers)) - { - Logger.Debug(string.Format("Unsubscribe request from {0} on message type {1} was refused.", subscriberAddress, messageTypeString)); - return; - } - - Logger.Info("Unsubscribing " + subscriberAddress + " from message type " + messageTypeString); - SubscriptionStorage.Unsubscribe(subscriberAddress, new[] - { - new MessageType(messageTypeString) - }); - } - - - static string GetSubscriptionMessageTypeFrom(TransportMessage msg) - { - return (from header in msg.Headers where header.Key == Headers.SubscriptionMessageType select header.Value).FirstOrDefault(); - } - - static ILog Logger = LogManager.GetLogger(); - IAuthorizeSubscriptions subscriptionAuthorizer; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Subscriptions/SubscriptionEventArgs.cs b/src/NServiceBus.Core/Unicast/Subscriptions/SubscriptionEventArgs.cs deleted file mode 100644 index 4b4579efe88..00000000000 --- a/src/NServiceBus.Core/Unicast/Subscriptions/SubscriptionEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Unicast.Subscriptions -{ - using System; - - /// - /// Contains which client subscribed to which message - /// - public class SubscriptionEventArgs : EventArgs - { - /// - /// The address of the subscriber. - /// - public Address SubscriberReturnAddress { get; set; } - - /// - /// The type of message the client subscribed to. - /// - public string MessageType { get; set; } - } -} diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/InboundTransport.cs b/src/NServiceBus.Core/Unicast/Transport/Config/InboundTransport.cs new file mode 100644 index 00000000000..1f757f8a0b6 --- /dev/null +++ b/src/NServiceBus.Core/Unicast/Transport/Config/InboundTransport.cs @@ -0,0 +1,14 @@ +namespace NServiceBus +{ + using Settings; + using Transport; + + class InboundTransport + { + public TransportReceiveInfrastructure Configure(ReadOnlySettings settings) + { + var transportInfrastructure = settings.Get(); + return transportInfrastructure.ConfigureReceiveInfrastructure(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/OutboundTransport.cs b/src/NServiceBus.Core/Unicast/Transport/Config/OutboundTransport.cs new file mode 100644 index 00000000000..bc75d6e5e24 --- /dev/null +++ b/src/NServiceBus.Core/Unicast/Transport/Config/OutboundTransport.cs @@ -0,0 +1,21 @@ +namespace NServiceBus +{ + using Settings; + using Transport; + + class OutboundTransport + { + public OutboundTransport(bool isDefault) + { + IsDefault = isDefault; + } + + public bool IsDefault { get; } + + public TransportSendInfrastructure Configure(ReadOnlySettings settings) + { + var transportInfrastructure = settings.Get(); + return transportInfrastructure.ConfigureSendInfrastructure(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/TransportConfiguration.cs b/src/NServiceBus.Core/Unicast/Transport/Config/TransportConfiguration.cs deleted file mode 100644 index 4b2de5dffa6..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/Config/TransportConfiguration.cs +++ /dev/null @@ -1,40 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global - -namespace NServiceBus -{ - using System; - - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public class TransportConfiguration - { - [ObsoleteEx( - Message = "Use `configuration.UseTransport().ConnectionString(connectionString)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public void ConnectionString(string connectionString) - { - throw new InvalidOperationException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseTransport().ConnectionStringName(name)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public void ConnectionStringName(string name) - { - throw new InvalidOperationException(); - } - - [ObsoleteEx( - Message = "Use` configuration.UseTransport().ConnectionString(connectionString)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public void ConnectionString(Func connectionString) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/TransportExtensions.cs b/src/NServiceBus.Core/Unicast/Transport/Config/TransportExtensions.cs index 84988af61d2..22cff6d6b72 100644 --- a/src/NServiceBus.Core/Unicast/Transport/Config/TransportExtensions.cs +++ b/src/NServiceBus.Core/Unicast/Transport/Config/TransportExtensions.cs @@ -1,19 +1,18 @@ namespace NServiceBus { using System; - using NServiceBus.Configuration.AdvanceExtensibility; - using NServiceBus.Settings; - using NServiceBus.Transports; - using NServiceBus.Unicast.Transport; + using Configuration.AdvanceExtensibility; + using Settings; + using Transport; /// /// This class provides implementers of persisters with an extension mechanism for custom settings via extension methods. /// - /// The persister definition eg , , etc + /// The persister definition eg , , etc. public class TransportExtensions : TransportExtensions where T : TransportDefinition { /// - /// Default constructor. + /// Initializes a new instance of . /// public TransportExtensions(SettingsHolder settings) : base(settings) @@ -21,90 +20,112 @@ public TransportExtensions(SettingsHolder settings) } /// - /// Configures the transport to use the given string as the connection string + /// Configures the transport to use the given string as the connection string. /// - public new TransportExtensions ConnectionString(string connectionString) + public new TransportExtensions ConnectionString(string connectionString) { base.ConnectionString(connectionString); return this; } /// - /// Configures the transport to use the connection string with the given name + /// Configures the transport to use the connection string with the given name. /// - public new TransportExtensions ConnectionStringName(string name) + public new TransportExtensions ConnectionStringName(string name) { base.ConnectionStringName(name); return this; } /// - /// Configures the transport to use the given func as the connection string + /// Configures the transport to use the given func as the connection string. /// - public new TransportExtensions ConnectionString(Func connectionString) + public new TransportExtensions ConnectionString(Func connectionString) { base.ConnectionString(connectionString); return this; } + + /// + /// Configures the transport to use a specific transaction mode. + /// + public new TransportExtensions Transactions(TransportTransactionMode transportTransactionMode) + { + base.Transactions(transportTransactionMode); + return this; + } } /// - /// This class provides implementers of transports with an extension mechanism for custom settings via extention methods. + /// This class provides implementers of transports with an extension mechanism for custom settings via extension methods. /// public class TransportExtensions : ExposeSettings { /// - /// Default constructor. + /// Initializes a new instance of . /// public TransportExtensions(SettingsHolder settings) : base(settings) { + settings.SetDefault(TransportConnectionString.Default); } /// - /// Configures the transport to use the given string as the connection string + /// Configures the transport to use the given string as the connection string. /// public TransportExtensions ConnectionString(string connectionString) { + Guard.AgainstNullAndEmpty(nameof(connectionString), connectionString); Settings.Set(new TransportConnectionString(() => connectionString)); return this; } /// - /// Configures the transport to use the connection string with the given name + /// Configures the transport to use the connection string with the given name. /// public TransportExtensions ConnectionStringName(string name) { + Guard.AgainstNullAndEmpty(nameof(name), name); Settings.Set(new TransportConnectionString(name)); return this; } /// - /// Configures the transport to use the given func as the connection string + /// Configures the transport to use the given func as the connection string. /// public TransportExtensions ConnectionString(Func connectionString) { + Guard.AgainstNull(nameof(connectionString), connectionString); Settings.Set(new TransportConnectionString(connectionString)); return this; } + + + /// + /// Configures the transport to use a explicit transaction mode. + /// + public TransportExtensions Transactions(TransportTransactionMode transportTransactionMode) + { + Settings.Set(transportTransactionMode); + return this; + } } /// - /// Allows you to read which transport connectionstring has been set + /// Allows you to read which transport connectionstring has been set. /// public static class ConfigureTransportConnectionString { /// /// Gets the transport connectionstring. /// + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Not available any more.")] public static string TransportConnectionString(this Configure config) { - TransportConnectionString conn; - if (config.Settings.TryGet(out conn)) - { - return conn.GetConnectionStringOrNull(); - } - return null; + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions.cs b/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions.cs index 7826fc3a178..5623b810e50 100644 --- a/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions.cs +++ b/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions.cs @@ -1,58 +1,54 @@ namespace NServiceBus { using System; - using NServiceBus.Utils; - using NServiceBus.Utils.Reflection; - using Transports; + using Transport; /// /// Extension methods to configure transport. /// - public static partial class UseTransportExtensions + public static class UseTransportExtensions { - private const string TransportDefinitionTypeKey = "transportDefinitionType"; - /// /// Configures NServiceBus to use the given transport. /// - public static TransportExtensions UseTransport(this BusConfiguration busConfiguration) where T : TransportDefinition, new() + public static TransportExtensions UseTransport(this EndpointConfiguration endpointConfiguration) where T : TransportDefinition, new() { + Guard.AgainstNull(nameof(endpointConfiguration), endpointConfiguration); var type = typeof(TransportExtensions<>).MakeGenericType(typeof(T)); - var extension = (TransportExtensions)Activator.CreateInstance(type, busConfiguration.Settings); - - busConfiguration.Settings.Set(TransportDefinitionTypeKey, typeof(T)); + var extension = (TransportExtensions) Activator.CreateInstance(type, endpointConfiguration.Settings); + var transportDefinition = new T(); + ConfigureTransport(endpointConfiguration, transportDefinition); return extension; } /// /// Configures NServiceBus to use the given transport. /// - public static TransportExtensions UseTransport(this BusConfiguration busConfiguration, Type transportDefinitionType) + public static TransportExtensions UseTransport(this EndpointConfiguration endpointConfiguration, Type transportDefinitionType) { - Guard.TypeHasDefaultConstructor(transportDefinitionType, "transportDefinitionType"); + Guard.AgainstNull(nameof(endpointConfiguration), endpointConfiguration); + Guard.AgainstNull(nameof(transportDefinitionType), transportDefinitionType); + Guard.TypeHasDefaultConstructor(transportDefinitionType, nameof(transportDefinitionType)); - busConfiguration.Settings.Set(TransportDefinitionTypeKey, transportDefinitionType); - - return new TransportExtensions(busConfiguration.Settings); + var transportDefinition = transportDefinitionType.Construct(); + ConfigureTransport(endpointConfiguration, transportDefinition); + return new TransportExtensions(endpointConfiguration.Settings); } - internal static void SetupTransport(BusConfiguration busConfiguration) + static void ConfigureTransport(EndpointConfiguration endpointConfiguration, TransportDefinition transportDefinition) { - var transportDefinition = GetTransportDefinition(busConfiguration); - busConfiguration.Settings.Set(transportDefinition); - transportDefinition.Configure(busConfiguration); + endpointConfiguration.Settings.Set(new InboundTransport()); + endpointConfiguration.Settings.Set(transportDefinition); + endpointConfiguration.Settings.Set(new OutboundTransport(true)); } - static TransportDefinition GetTransportDefinition(BusConfiguration busConfiguration) + internal static void EnsureTransportConfigured(EndpointConfiguration endpointConfiguration) { - Type transportDefinitionType; - if (!busConfiguration.Settings.TryGet(TransportDefinitionTypeKey, out transportDefinitionType)) + if (!endpointConfiguration.Settings.HasExplicitValue()) { - return new MsmqTransport(); + endpointConfiguration.UseTransport(); } - - return transportDefinitionType.Construct(); } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions_Obsolete.cs b/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions_Obsolete.cs deleted file mode 100644 index 310921672d0..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/Config/UseTransportExtensions_Obsolete.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus -{ - using System; - using Transports; - - public static partial class UseTransportExtensions - { - [ObsoleteEx( - Message = "Use `configuration.UseTransport()`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure UseTransport(this Configure config, Action customizations = null) where T : TransportDefinition - { - throw new InvalidOperationException(); - } - - [ObsoleteEx( - Message = "Use `configuration.UseTransport(transportDefinitionType)`, where `configuration` is an instance of `BusConfiguration`. If self-hosting the instance can be obtained from `new BusConfiguration()`. if using the NServiceBus Host the instance of `BusConfiguration` will be passed in via the `INeedInitialization` or `IConfigureThisEndpoint` interfaces.", - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0")] - public static Configure UseTransport(this Configure config, Type transportDefinitionType, Action customizations = null) - { - throw new InvalidOperationException(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/ControlMessage.cs b/src/NServiceBus.Core/Unicast/Transport/ControlMessage.cs deleted file mode 100644 index f4dbb3d9f9b..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/ControlMessage.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - /// - /// Helper for creating control messages - /// - public static class ControlMessage - { - /// - /// Creates Transport Message - /// - /// Transport Message - public static TransportMessage Create() - { - var transportMessage = new TransportMessage - { - Recoverable = true, - }; - transportMessage.Headers.Add(Headers.ControlMessageHeader, true.ToString()); - - return transportMessage; - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Transport/ControlMessageFactory.cs b/src/NServiceBus.Core/Unicast/Transport/ControlMessageFactory.cs new file mode 100644 index 00000000000..f2c5a7dd4b9 --- /dev/null +++ b/src/NServiceBus.Core/Unicast/Transport/ControlMessageFactory.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Unicast.Transport +{ + using System; + using System.Collections.Generic; + using NServiceBus.Transport; + + /// + /// Helper for creating control messages. + /// + public static class ControlMessageFactory + { + /// + /// Creates Transport Message. + /// + /// Transport Message. + public static OutgoingMessage Create(MessageIntentEnum intent) + { + var message = new OutgoingMessage(CombGuid.Generate().ToString(), new Dictionary(), new byte[0]); + message.Headers[Headers.ControlMessageHeader] = Boolean.TrueString; + message.Headers[Headers.MessageIntent] = intent.ToString(); + + return message; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/FailedMessageProcessingEventArgs.cs b/src/NServiceBus.Core/Unicast/Transport/FailedMessageProcessingEventArgs.cs deleted file mode 100644 index c3a0f358213..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/FailedMessageProcessingEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - - /// - /// Defines the event data for the failed message processing event - /// - public class FailedMessageProcessingEventArgs : EventArgs - { - /// - /// The exception that caused the processing to fail - /// - public Exception Reason { get; private set; } - - /// - /// Gets the message received. - /// - public TransportMessage Message { get; private set; } - - /// - /// Initialized the event arg with the actual exception - /// - public FailedMessageProcessingEventArgs(TransportMessage m, Exception ex) - { - Message = m; - Reason = ex; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/FirstLevelRetries.cs b/src/NServiceBus.Core/Unicast/Transport/FirstLevelRetries.cs deleted file mode 100644 index 25481b0a969..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/FirstLevelRetries.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - using System.Collections.Concurrent; - using Faults; - - class FirstLevelRetries - { - ConcurrentDictionary> failuresPerMessage = new ConcurrentDictionary>(); - IManageMessageFailures failureManager; - CriticalError criticalError; - readonly BusNotifications notifications; - int maxRetries; - - public FirstLevelRetries(int maxRetries, IManageMessageFailures failureManager, CriticalError criticalError, BusNotifications busNotifications) - { - this.maxRetries = maxRetries; - this.failureManager = failureManager; - this.criticalError = criticalError; - notifications = busNotifications; - } - - public bool HasMaxRetriesForMessageBeenReached(TransportMessage message) - { - var messageId = message.Id; - Tuple e; - - if (failuresPerMessage.TryGetValue(messageId, out e)) - { - if (e.Item1 < maxRetries) - { - return false; - } - - TryInvokeFaultManager(message, e.Item2, e.Item1); - ClearFailuresForMessage(message); - - return true; - } - - return false; - } - - public void ClearFailuresForMessage(TransportMessage message) - { - var messageId = message.Id; - Tuple e; - failuresPerMessage.TryRemove(messageId, out e); - } - - public void IncrementFailuresForMessage(TransportMessage message, Exception e) - { - var item = failuresPerMessage.AddOrUpdate(message.Id, s => new Tuple(1, e), - (s, i) => new Tuple(i.Item1 + 1, e)); - - notifications.Errors.InvokeMessageHasFailedAFirstLevelRetryAttempt(item.Item1, message, e); - } - - void TryInvokeFaultManager(TransportMessage message, Exception exception, int numberOfAttempts) - { - try - { - message.RevertToOriginalBodyIfNeeded(); - var numberOfRetries = numberOfAttempts - 1; - message.Headers[Headers.FLRetries] = numberOfRetries.ToString(); - failureManager.ProcessingAlwaysFailsForMessage(message, exception); - } - catch (Exception ex) - { - criticalError.Raise(String.Format("Fault manager failed to process the failed message with id {0}", message.Id), ex); - - throw; - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/ITransport.cs b/src/NServiceBus.Core/Unicast/Transport/ITransport.cs deleted file mode 100644 index 7294a5402ae..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/ITransport.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - - /// - /// Defines the basic functionality of a transport to be used by NServiceBus. - /// - public interface ITransport - { - - /// - /// Starts the transport listening for messages on the given local address. - /// - void Start(Address localAddress); - - /// - /// Gets the maximum concurrency level this is able to support. - /// - int MaximumConcurrencyLevel { get; } - - /// - /// Updates the maximum concurrency level this is able to support. - /// - /// The new maximum concurrency level for this . - void ChangeMaximumConcurrencyLevel(int maximumConcurrencyLevel); - - /// - /// Gets the receiving messages rate. - /// - int MaximumMessageThroughputPerSecond { get; } - - /// - /// Updates the max throughput per second. - /// - /// The new max throughput. - void ChangeMaximumMessageThroughputPerSecond(int maximumMessageThroughputPerSecond); - - /// - /// Raised when a message is received. - /// - event EventHandler TransportMessageReceived; - - /// - /// Raised when a message is available but before is raised. - /// - event EventHandler StartedMessageProcessing; - - /// - /// Raised after message processing was completed, even in case of an exception in message processing. - /// - event EventHandler FinishedMessageProcessing; - - /// - /// Raised if an exception was encountered at any point in the processing - including - /// when the transaction commits. - /// - event EventHandler FailedMessageProcessing; - - /// - /// Causes the current message being handled to return to the queue. - /// - void AbortHandlingCurrentMessage(); - - /// - /// Stops the transport. - /// - void Stop(); - } -} diff --git a/src/NServiceBus.Core/Unicast/Transport/Monitoring/ReceivePerformanceDiagnostics.cs b/src/NServiceBus.Core/Unicast/Transport/Monitoring/ReceivePerformanceDiagnostics.cs deleted file mode 100644 index 30f3451f01a..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/Monitoring/ReceivePerformanceDiagnostics.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace NServiceBus.Unicast.Transport.Monitoring -{ - using System; - using System.Diagnostics; - using Logging; - - class ReceivePerformanceDiagnostics : IDisposable - { - static ILog Logger = LogManager.GetLogger(); - readonly Address receiveAddress; - bool enabled; - PerformanceCounter failureRateCounter; - PerformanceCounter successRateCounter; - PerformanceCounter throughputCounter; - - public ReceivePerformanceDiagnostics(Address receiveAddress) - { - this.receiveAddress = receiveAddress; - } - - public void Dispose() - { - //Injected at compile time - } - - public void Initialize() - { - if (!InstantiateCounter()) - { - return; - } - - enabled = true; - } - - public void MessageProcessed() - { - if (!enabled) - { - return; - } - - successRateCounter.Increment(); - } - - public void MessageFailed() - { - if (!enabled) - { - return; - } - - failureRateCounter.Increment(); - } - - public void MessageDequeued() - { - if (!enabled) - { - return; - } - - throughputCounter.Increment(); - } - - bool InstantiateCounter() - { - return SetupCounter("# of msgs successfully processed / sec", out successRateCounter) - && SetupCounter("# of msgs pulled from the input queue /sec", out throughputCounter) - && SetupCounter("# of msgs failures / sec", out failureRateCounter); - } - - bool SetupCounter(string counterName, out PerformanceCounter counter) - { - if (!PerformanceCounterHelper.TryToInstantiatePerformanceCounter(counterName, receiveAddress.Queue, out counter)) - { - return false; - } - - Logger.DebugFormat("'{0}' counter initialized for '{1}'", counterName, receiveAddress); - - return true; - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Transport/StartedMessageProcessingEventArgs.cs b/src/NServiceBus.Core/Unicast/Transport/StartedMessageProcessingEventArgs.cs deleted file mode 100644 index a6d1d86e9be..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/StartedMessageProcessingEventArgs.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - - /// - /// Defines the arguments passed to the event handler of the - /// event. - /// - public class StartedMessageProcessingEventArgs : EventArgs - { - /// - /// Initializes a new TransportMessageReceivedEventArgs. - /// - /// The message that was received. - public StartedMessageProcessingEventArgs(TransportMessage m) - { - message = m; - } - - readonly TransportMessage message; - - /// - /// Gets the message received. - /// - public TransportMessage Message - { - get { return message; } - } - } - - /// - /// Defines the arguments passed to the event handler of the - /// event. - /// - public class FinishedMessageProcessingEventArgs : EventArgs - { - /// - /// Initializes a new FinishedMessageProcessingEventArgs. - /// - /// The message that was received. - public FinishedMessageProcessingEventArgs(TransportMessage m) - { - message = m; - } - - readonly TransportMessage message; - - /// - /// Gets the message received. - /// - public TransportMessage Message - { - get { return message; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/ThroughputLimiter.cs b/src/NServiceBus.Core/Unicast/Transport/ThroughputLimiter.cs deleted file mode 100644 index ee0eeb3a894..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/ThroughputLimiter.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - using System.Threading; - - /// - /// Support for throughput limitation of the transport - /// - class ThroughputLimiter:IDisposable - { - public void Start(int limit) - { - if (limit <= 0) - { - return; - } - - numberOfMessagesProcessed = 0; - throughputSemaphore = new SemaphoreSlim(limit, limit); - cancellationTokenSource = new CancellationTokenSource(); - timer = new Timer(ResetLimit, null, 1000, 1000); - started = true; - } - - public void Stop() - { - if (!started) - { - return; - } - - started = false; - - using (var waitHandle = new ManualResetEvent(false)) - { - timer.Dispose(waitHandle); - - waitHandle.WaitOne(); - } - - cancellationTokenSource.Cancel(); - - BlockUntilZeroMessagesBeingProcessed(); - - throughputSemaphore.Dispose(); - cancellationTokenSource.Dispose(); - } - - void BlockUntilZeroMessagesBeingProcessed() - { - while (numberOfMessagesProcessing > 0) - { - Thread.SpinWait(5); - } - } - - public void MessageProcessed() - { - if (!started) - { - return; - } - - try - { - Interlocked.Increment(ref numberOfMessagesProcessing); - throughputSemaphore.Wait(cancellationTokenSource.Token); - Interlocked.Increment(ref numberOfMessagesProcessed); - } - catch (OperationCanceledException) - { - } - finally - { - Interlocked.Decrement(ref numberOfMessagesProcessing); - } - } - - void ResetLimit(object state) - { - var numberOfMessagesProcessedSnapshot = Interlocked.Exchange(ref numberOfMessagesProcessed, 0); - - if (numberOfMessagesProcessedSnapshot > 0) - { - throughputSemaphore.Release(numberOfMessagesProcessedSnapshot); - } - } - - CancellationTokenSource cancellationTokenSource; - int numberOfMessagesProcessed; - int numberOfMessagesProcessing; - bool started; - SemaphoreSlim throughputSemaphore; - Timer timer; - - public void Dispose() - { - //Injected - } - void DisposeManaged() - { - Stop(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/TransactionSettings.cs b/src/NServiceBus.Core/Unicast/Transport/TransactionSettings.cs deleted file mode 100644 index 36838dd64cc..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/TransactionSettings.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - using System.Transactions; - using Settings; - - /// - /// Settings relates to transactions - /// - public class TransactionSettings - { - internal TransactionSettings(ReadOnlySettings settings) - { - MaxRetries = 5; - IsTransactional = settings.Get("Transactions.Enabled"); - TransactionTimeout = settings.Get("Transactions.DefaultTimeout"); - IsolationLevel = settings.Get("Transactions.IsolationLevel"); - SuppressDistributedTransactions = settings.Get("Transactions.SuppressDistributedTransactions"); - DoNotWrapHandlersExecutionInATransactionScope = settings.Get("Transactions.DoNotWrapHandlersExecutionInATransactionScope"); - } - - /// - /// Create a new settings - /// - /// Is transactions on - /// The tx timeout - /// The isolation level - /// The number of FLR retries - /// Should DTC be suppressed - /// Should handlers be wrapped - public TransactionSettings(bool isTransactional, TimeSpan transactionTimeout, IsolationLevel isolationLevel, int maxRetries, bool suppressDistributedTransactions, bool doNotWrapHandlersExecutionInATransactionScope) - { - IsTransactional = isTransactional; - TransactionTimeout = transactionTimeout; - IsolationLevel = isolationLevel; - MaxRetries = maxRetries; - SuppressDistributedTransactions = suppressDistributedTransactions; - DoNotWrapHandlersExecutionInATransactionScope = doNotWrapHandlersExecutionInATransactionScope; - } - - - /// - /// Sets whether or not the transport is transactional. - /// - public bool IsTransactional { get; set; } - - /// - /// Property for getting/setting the period of time when the transaction times out. - /// Only relevant when is set to true. - /// - public TimeSpan TransactionTimeout { get; set; } - - /// - /// Property for getting/setting the isolation level of the transaction scope. - /// Only relevant when is set to true. - /// - public IsolationLevel IsolationLevel { get; set; } - - /// - /// Sets the maximum number of times a message will be retried - /// when an exception is thrown as a result of handling the message. - /// This value is only relevant when is true. - /// - /// - /// Default value is 5. - /// - public int MaxRetries { get; set; } - - /// - /// If true the transport won't enlist in distributed transactions - /// - public bool SuppressDistributedTransactions { get; set; } - - /// - /// Controls if the message handlers should be wrapped in a - /// - public bool DoNotWrapHandlersExecutionInATransactionScope { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/TransportConnectionString.cs b/src/NServiceBus.Core/Unicast/Transport/TransportConnectionString.cs index e28b06c3b7c..189f5b10b77 100644 --- a/src/NServiceBus.Core/Unicast/Transport/TransportConnectionString.cs +++ b/src/NServiceBus.Core/Unicast/Transport/TransportConnectionString.cs @@ -1,35 +1,15 @@ -namespace NServiceBus.Unicast.Transport +namespace NServiceBus { using System; using System.Configuration; + using Transport; - class TransportConnectionString + sealed class TransportConnectionString { - protected TransportConnectionString() + TransportConnectionString() { } - public string GetConnectionStringOrNull() - { - return GetValue(); - } - - Func GetValue = () => ReadConnectionString(DefaultConnectionStringName); - - - static string ReadConnectionString(string connectionStringName) - { - var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; - - if (connectionStringSettings == null) - { - return null; - } - - return connectionStringSettings.ConnectionString; - } - - public TransportConnectionString(Func func) { GetValue = func; @@ -41,16 +21,42 @@ public TransportConnectionString(string name) GetValue = () => ReadConnectionString(name); } - public static TransportConnectionString Default + public static TransportConnectionString Default => new TransportConnectionString(); + + public string GetConnectionStringOrRaiseError(TransportDefinition transportDefinition) { - get + var connectionString = GetValue(); + if (connectionString == null && transportDefinition.RequiresConnectionString) { - return new TransportConnectionString(); + throw new InvalidOperationException(string.Format(Message, transportDefinition.GetType().Name, transportDefinition.ExampleConnectionStringForErrorMessage)); } + return connectionString; + } + + static string ReadConnectionString(string connectionStringName) + { + var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName]; + return connectionStringSettings?.ConnectionString; } - const string DefaultConnectionStringName = "NServiceBus/Transport"; + Func GetValue = () => ReadConnectionString(DefaultConnectionStringName); + + const string Message = + @"Transport connection string has not been explicitly configured via ConnectionString method and no default connection has been was found in the app.config or web.config file for the {0} Transport. + +To run NServiceBus with {0} Transport you need to specify the database connectionstring. +Here are examples of what is required: + + + + +or + + busConfig.UseTransport<{0}>().ConnectionString(""{1}""); +"; + + const string DefaultConnectionStringName = "NServiceBus/Transport"; } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/TransportMessageAvailableEventArgs.cs b/src/NServiceBus.Core/Unicast/Transport/TransportMessageAvailableEventArgs.cs deleted file mode 100644 index d815a59fa26..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/TransportMessageAvailableEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - - /// - /// Provides data for the MessageDequeued event. - /// - public class TransportMessageAvailableEventArgs : EventArgs - { - private readonly TransportMessage message; - - /// - /// Default constructor for . - /// - /// - /// The received . - /// - public TransportMessageAvailableEventArgs(TransportMessage m) - { - message = m; - } - - /// - /// The received . - /// - public TransportMessage Message - { - get { return message; } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/TransportMessageExtensions.cs b/src/NServiceBus.Core/Unicast/Transport/TransportMessageExtensions.cs deleted file mode 100644 index 14e911c16aa..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/TransportMessageExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using Messages; - - static class TransportMessageExtensions - { - public static bool IsControlMessage(this TransportMessage transportMessage) - { - return transportMessage.Headers != null && - transportMessage.Headers.ContainsKey(Headers.ControlMessageHeader); - } - - - public static bool IsControlMessage(this LogicalMessage transportMessage) - { - return transportMessage.Headers != null && - transportMessage.Headers.ContainsKey(Headers.ControlMessageHeader); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/Transport/TransportMessageReceivedEventArgs.cs b/src/NServiceBus.Core/Unicast/Transport/TransportMessageReceivedEventArgs.cs deleted file mode 100644 index 387a31743e9..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/TransportMessageReceivedEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - - /// - /// Defines the arguments passed to the event handler of the - /// event. - /// - public class TransportMessageReceivedEventArgs : EventArgs - { - /// - /// Initializes a new TransportMessageReceivedEventArgs. - /// - /// The message that was received. - public TransportMessageReceivedEventArgs(TransportMessage m) - { - message = m; - } - - private readonly TransportMessage message; - - /// - /// Gets the message received. - /// - public TransportMessage Message - { - get { return message; } - } - } -} diff --git a/src/NServiceBus.Core/Unicast/Transport/TransportReceiver.cs b/src/NServiceBus.Core/Unicast/Transport/TransportReceiver.cs deleted file mode 100644 index 1b5c0da57d5..00000000000 --- a/src/NServiceBus.Core/Unicast/Transport/TransportReceiver.cs +++ /dev/null @@ -1,461 +0,0 @@ -namespace NServiceBus.Unicast.Transport -{ - using System; - using System.Diagnostics; - using System.Runtime.Serialization; - using System.Transactions; - using Faults; - using Logging; - using Monitoring; - using Settings; - using Transports; - - /// - /// The default implementation of - /// - public class TransportReceiver : ITransport, IDisposable - { - /// - /// Creates an instance of - /// - /// The transaction settings to use for this . - /// The maximum number of messages to process in parallel. - /// The maximum throughput per second, 0 means unlimited. - /// The instance to use. - /// The instance to use. - /// The current settings - /// Configure instance - public TransportReceiver(TransactionSettings transactionSettings, int maximumConcurrencyLevel, int maximumThroughput, IDequeueMessages receiver, IManageMessageFailures manageMessageFailures, ReadOnlySettings settings, Configure config) - { - this.settings = settings; - this.config = config; - TransactionSettings = transactionSettings; - MaximumConcurrencyLevel = maximumConcurrencyLevel; - MaximumMessageThroughputPerSecond = maximumThroughput; - FailureManager = manageMessageFailures; - Receiver = receiver; - } - - internal BusNotifications Notifications { get; set; } - - /// - /// The receiver responsible for notifying the transport when new messages are available - /// - public IDequeueMessages Receiver { get; set; } - - /// - /// Manages failed message processing. - /// - public IManageMessageFailures FailureManager { get; set; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - //Injected at compile time - } - - /// - /// Event which indicates that message processing has started. - /// - public event EventHandler StartedMessageProcessing; - - /// - /// Event which indicates that message processing has completed. - /// - public event EventHandler FinishedMessageProcessing; - - /// - /// Event which indicates that message processing failed for some reason. - /// - public event EventHandler FailedMessageProcessing; - - /// - /// Gets the maximum concurrency level this is able to support. - /// - public virtual int MaximumConcurrencyLevel { get; private set; } - - /// - /// Updates the maximum concurrency level this is able to support. - /// - /// The new maximum concurrency level for this . - public void ChangeMaximumConcurrencyLevel(int maximumConcurrencyLevel) - { - if (MaximumConcurrencyLevel == maximumConcurrencyLevel) - { - return; - } - - MaximumConcurrencyLevel = maximumConcurrencyLevel; - - if (isStarted) - { - Receiver.Stop(); - Receiver.Start(maximumConcurrencyLevel); - Logger.InfoFormat("Maximum concurrency level for '{0}' changed to {1}.", receiveAddress, - maximumConcurrencyLevel); - } - } - - /// - /// Gets the receiving messages rate. - /// - public int MaximumMessageThroughputPerSecond { get; private set; } - - /// - /// Updates the MaximumMessageThroughputPerSecond setting. - /// - /// The new value. - public void ChangeMaximumMessageThroughputPerSecond(int maximumMessageThroughputPerSecond) - { - if (maximumMessageThroughputPerSecond == MaximumMessageThroughputPerSecond) - { - return; - } - - lock (changeMaximumMessageThroughputPerSecondLock) - { - MaximumMessageThroughputPerSecond = maximumMessageThroughputPerSecond; - if (throughputLimiter != null) - { - throughputLimiter.Stop(); - throughputLimiter.Start(maximumMessageThroughputPerSecond); - } - } - if (maximumMessageThroughputPerSecond <= 0) - { - Logger.InfoFormat("Throughput limit for {0} disabled.", receiveAddress); - } - else - { - Logger.InfoFormat("Throughput limit for {0} changed to {1} msg/sec", receiveAddress, - maximumMessageThroughputPerSecond); - } - } - - /// - /// Event raised when a message has been received in the input queue. - /// - public event EventHandler TransportMessageReceived; - - - /// - /// Starts the transport listening for messages on the given local address. - /// - public void Start(Address address) - { - if (isStarted) - { - throw new InvalidOperationException("The transport is already started"); - } - - receiveAddress = address; - - var returnAddressForFailures = address; - - var workerRunsOnThisEndpoint = settings.GetOrDefault("Worker.Enabled"); - - if (workerRunsOnThisEndpoint - && (returnAddressForFailures.Queue.ToLower().EndsWith(".worker") || address == config.LocalAddress)) - //this is a hack until we can refactor the SLR to be a feature. "Worker" is there to catch the local worker in the distributor - { - returnAddressForFailures = settings.Get
("MasterNode.Address"); - - Logger.InfoFormat("Worker started, failures will be redirected to {0}", returnAddressForFailures); - } - - FailureManager.Init(returnAddressForFailures); - - firstLevelRetries = new FirstLevelRetries(TransactionSettings.MaxRetries, FailureManager, CriticalError, Notifications); - - InitializePerformanceCounters(); - - throughputLimiter = new ThroughputLimiter(); - - throughputLimiter.Start(MaximumMessageThroughputPerSecond); - - StartReceiver(); - - if (MaximumMessageThroughputPerSecond > 0) - { - Logger.InfoFormat("Transport: {0} started with its throughput limited to {1} msg/sec", receiveAddress, - MaximumMessageThroughputPerSecond); - } - - isStarted = true; - } - - /// - /// Causes the processing of the current message to be aborted. - /// - public void AbortHandlingCurrentMessage() - { - needToAbort = true; - } - - /// - /// Stops the transport. - /// - public void Stop() - { - InnerStop(); - } - - void InitializePerformanceCounters() - { - currentReceivePerformanceDiagnostics = new ReceivePerformanceDiagnostics(receiveAddress); - - currentReceivePerformanceDiagnostics.Initialize(); - } - - void StartReceiver() - { - Receiver.Init(receiveAddress, TransactionSettings, TryProcess, EndProcess); - Receiver.Start(MaximumConcurrencyLevel); - } - - [DebuggerNonUserCode] - bool TryProcess(TransportMessage message) - { - currentReceivePerformanceDiagnostics.MessageDequeued(); - - needToAbort = false; - - using (var tx = GetTransactionScope()) - { - ProcessMessage(message); - - tx.Complete(); - } - - return !needToAbort; - } - - TransactionScope GetTransactionScope() - { - if (TransactionSettings.DoNotWrapHandlersExecutionInATransactionScope) - { - return new TransactionScope(TransactionScopeOption.Suppress); - } - - return new TransactionScope(TransactionScopeOption.Required, new TransactionOptions - { - IsolationLevel = TransactionSettings.IsolationLevel, - Timeout = TransactionSettings.TransactionTimeout - }); - } - - - void EndProcess(TransportMessage message, Exception ex) - { - var messageId = message != null ? message.Id : null; - - if (needToAbort) - { - return; - } - - throughputLimiter.MessageProcessed(); - - if (ex == null) - { - if (messageId != null) - { - firstLevelRetries.ClearFailuresForMessage(message); - } - - currentReceivePerformanceDiagnostics.MessageProcessed(); - - return; - } - - currentReceivePerformanceDiagnostics.MessageFailed(); - - if (TransactionSettings.IsTransactional && messageId != null) - { - firstLevelRetries.IncrementFailuresForMessage(message, ex); - } - - OnFailedMessageProcessing(message, ex); - - Logger.Info("Failed to process message", ex); - } - - void ProcessMessage(TransportMessage message) - { - try - { - OnStartedMessageProcessing(message); - } - catch (Exception exception) - { - Logger.Error("Failed raising 'started message processing' event.", exception); - if (ShouldExitBecauseOfRetries(message)) - { - return; - } - throw; - } - - if (string.IsNullOrWhiteSpace(message.Id)) - { - Logger.Error("Message without message id detected"); - - FailureManager.SerializationFailedForMessage(message, - new SerializationException("Message without message id received.")); - - return; - } - - - if (ShouldExitBecauseOfRetries(message)) - { - try - { - OnFinishedMessageProcessing(message); - } - catch (Exception exception) - { - Logger.Error("Failed raising 'finished message processing' event.", exception); - } - return; - } - - try - { - OnTransportMessageReceived(message); - } - catch (MessageDeserializationException serializationException) - { - Logger.Error("Failed to deserialize message with ID: " + message.Id, serializationException); - - message.RevertToOriginalBodyIfNeeded(); - - FailureManager.SerializationFailedForMessage(message, serializationException); - } - catch (Exception) - { - //but need to abort takes precedence - failures aren't counted here, - //so messages aren't moved to the error queue. - if (!needToAbort) - { - throw; - } - } - finally - { - try - { - OnFinishedMessageProcessing(message); - } - catch (Exception exception) - { - Logger.Error("Failed raising 'finished message processing' event.", exception); - //but need to abort takes precedence - failures aren't counted here, - //so messages aren't moved to the error queue. - if (!needToAbort) - { - throw; - } - } - } - } - - bool ShouldExitBecauseOfRetries(TransportMessage message) - { - if (TransactionSettings.IsTransactional) - { - if (firstLevelRetries.HasMaxRetriesForMessageBeenReached(message)) - { - return true; - } - } - return false; - } - - void InnerStop() - { - if (!isStarted) - { - return; - } - - Receiver.Stop(); - throughputLimiter.Stop(); - - isStarted = false; - } - - void OnStartedMessageProcessing(TransportMessage msg) - { - if (StartedMessageProcessing != null) - { - StartedMessageProcessing(this, new StartedMessageProcessingEventArgs(msg)); - } - } - - void OnFinishedMessageProcessing(TransportMessage msg) - { - if (FinishedMessageProcessing != null) - { - FinishedMessageProcessing(this, new FinishedMessageProcessingEventArgs(msg)); - } - } - - void OnTransportMessageReceived(TransportMessage msg) - { - if (TransportMessageReceived != null) - { - TransportMessageReceived(this, new TransportMessageReceivedEventArgs(msg)); - } - } - - void OnFailedMessageProcessing(TransportMessage message, Exception originalException) - { - try - { - if (FailedMessageProcessing != null) - { - FailedMessageProcessing(this, new FailedMessageProcessingEventArgs(message, originalException)); - } - } - catch (Exception e) - { - Logger.Warn("Failed raising 'failed message processing' event.", e); - } - } - - void DisposeManaged() - { - InnerStop(); - - if (currentReceivePerformanceDiagnostics != null) - { - currentReceivePerformanceDiagnostics.Dispose(); - } - } - - - [ThreadStatic] - static volatile bool needToAbort; - - static ILog Logger = LogManager.GetLogger(); - object changeMaximumMessageThroughputPerSecondLock = new object(); - ReceivePerformanceDiagnostics currentReceivePerformanceDiagnostics; - FirstLevelRetries firstLevelRetries; - bool isStarted; - Address receiveAddress; - ThroughputLimiter throughputLimiter; - readonly ReadOnlySettings settings; - readonly Configure config; - - /// - /// The being used. - /// - public TransactionSettings TransactionSettings { get; private set; } - - internal CriticalError CriticalError { get; set; } - } -} diff --git a/src/NServiceBus.Core/Unicast/UnicastBus.cs b/src/NServiceBus.Core/Unicast/UnicastBus.cs deleted file mode 100644 index f4c8db528ac..00000000000 --- a/src/NServiceBus.Core/Unicast/UnicastBus.cs +++ /dev/null @@ -1,936 +0,0 @@ -namespace NServiceBus.Unicast -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Security.Principal; - using System.Threading; - using System.Threading.Tasks; - using Hosting; - using Licensing; - using Logging; - using MessageInterfaces; - using Messages; - using ObjectBuilder; - using Pipeline; - using Pipeline.Contexts; - using Routing; - using Satellites; - using Settings; - using Transport; - using Transports; - - /// - /// A unicast implementation of for NServiceBus. - /// - public partial class UnicastBus : IStartableBus, IInMemoryOperations, IManageMessageHeaders - { - HostInformation hostInformation; - - /// - /// Initializes a new instance of . - /// - public UnicastBus() - { - SetupHeaderActions(); - } - - void SetupHeaderActions() - { - SetHeaderAction = (message, key, value) => - { - //are we in the process of sending a logical message - var outgoingLogicalMessageContext = PipelineFactory.CurrentContext as OutgoingContext; - - if (outgoingLogicalMessageContext != null && outgoingLogicalMessageContext.OutgoingLogicalMessage.Instance == message) - { - outgoingLogicalMessageContext.OutgoingLogicalMessage.Headers[key] = value; - } - - Dictionary> outgoingHeaders; - - if (!PipelineFactory.CurrentContext.TryGet("NServiceBus.OutgoingHeaders", out outgoingHeaders)) - { - outgoingHeaders = new Dictionary>(); - - PipelineFactory.CurrentContext.Set("NServiceBus.OutgoingHeaders", outgoingHeaders); - } - - Dictionary outgoingHeadersForThisMessage; - - if (!outgoingHeaders.TryGetValue(message, out outgoingHeadersForThisMessage)) - { - outgoingHeadersForThisMessage = new Dictionary(); - outgoingHeaders[message] = outgoingHeadersForThisMessage; - } - - outgoingHeadersForThisMessage[key] = value; - }; - - GetHeaderAction = (message, key) => - { - if (message == ExtensionMethods.CurrentMessageBeingHandled) - { - LogicalMessage messageBeingReceived; - - //first try to get the header from the current logical message - if (PipelineFactory.CurrentContext.TryGet(out messageBeingReceived)) - { - string value; - - if (messageBeingReceived.Headers.TryGetValue(key, out value)) - { - return value; - } - } - - //falling back to get the headers from the physical message - // when we remove the multi message feature we can remove this and instead - // share the same header collection btw physical and logical message - if (CurrentMessageContext != null) - { - string value; - if (CurrentMessageContext.Headers.TryGetValue(key, out value)) - { - return value; - } - } - } - - Dictionary> outgoingHeaders; - - if (!PipelineFactory.CurrentContext.TryGet("NServiceBus.OutgoingHeaders", out outgoingHeaders)) - { - return null; - } - Dictionary outgoingHeadersForThisMessage; - - if (!outgoingHeaders.TryGetValue(message, out outgoingHeadersForThisMessage)) - { - return null; - } - - string headerValue; - - outgoingHeadersForThisMessage.TryGetValue(key, out headerValue); - - return headerValue; - }; - } - - /// - /// The used to set the header in the bus.SetMessageHeader(msg, key, value) method. - /// - public Action SetHeaderAction { get; internal set; } - - /// - /// The used to get the header value in the bus.GetMessageHeader(msg, key) method. - /// - public Func GetHeaderAction { get; internal set; } - - /// - /// Provides access to the current host information - /// - [ObsoleteEx(Message = "We have introduced a more explicit API to set the host identifier, see busConfiguration.UniquelyIdentifyRunningInstance()", TreatAsErrorFromVersion = "6", RemoveInVersion = "7")] - public HostInformation HostInformation - { - get { return hostInformation; } - set - { - if (value == null) - { - throw new ArgumentNullException(); - } - - hostInformation = value; - } - } - - /// - /// Access to the current settings - /// - public ReadOnlySettings Settings { get; set; } - - /// - /// Sets an implementation to use as the - /// listening endpoint for the bus. - /// - public ITransport Transport { get; set; } - - /// - /// Critical error handling - /// - public CriticalError CriticalError { get; set; } - - /// - /// Message queue used to send messages. - /// - public ISendMessages MessageSender { get; set; } - - /// - /// Configuration. - /// - public Configure Configure { get; set; } - - /// - /// Sets implementation that will be used to - /// dynamically instantiate and execute message handlers. - /// - public IBuilder Builder { get; set; } - - /// - /// Gets/sets the message mapper. - /// - public IMessageMapper MessageMapper - { - get { return messageMapper; } - set { messageMapper = value; } - } - - /// - /// Sets whether or not the return address of a received message - /// should be propagated when the message is forwarded. This field is - /// used primarily for the Distributor. - /// - public bool PropagateReturnAddressOnSend { get; set; } - - /// - /// The router for this - /// - public StaticMessageRouter MessageRouter { get; set; } - - /// - /// The registered subscription manager for this bus instance - /// - public IManageSubscriptions SubscriptionManager { get; set; } - - /// - /// - /// - public void Publish(Action messageConstructor) - { - Publish(messageMapper.CreateInstance(messageConstructor)); - } - - /// - /// - /// - public virtual void Publish() - { - Publish(messageMapper.CreateInstance()); - } - - /// - /// - /// - public virtual void Publish(T message) - { - var logicalMessage = LogicalMessageFactory.Create(message); - var options = new PublishOptions(logicalMessage.MessageType); - InvokeSendPipeline(options, logicalMessage); - } - - /// - /// - /// - public void Subscribe() - { - Subscribe(typeof(T)); - } - - bool SendOnlyMode { get { return Settings.Get("Endpoint.SendOnly"); } } - - /// - /// - /// - public virtual void Subscribe(Type messageType) - { - MessagingBestPractices.AssertIsValidForPubSub(messageType, Builder.Build()); - - if (SendOnlyMode) - { - throw new InvalidOperationException("It's not allowed for a send only endpoint to be a subscriber"); - } - - AssertHasLocalAddress(); - - if (SubscriptionManager == null) - { - throw new InvalidOperationException("No subscription manager is available"); - } - - if (TransportDefinition.HasSupportForCentralizedPubSub) - { - // We are dealing with a brokered transport wired for auto pub/sub. - SubscriptionManager.Subscribe(messageType, null); - return; - } - - var addresses = GetAtLeastOneAddressForMessageType(messageType); - - foreach (var destination in addresses) - { - if (Address.Self == destination) - { - throw new InvalidOperationException(string.Format("Message {0} is owned by the same endpoint that you're trying to subscribe", messageType)); - } - - SubscriptionManager.Subscribe(messageType, destination); - } - } - - List
GetAtLeastOneAddressForMessageType(Type messageType) - { - var addresses = GetAddressForMessageType(messageType) - .Distinct() - .ToList(); - if (addresses.Count == 0) - { - var error = string.Format("No destination could be found for message type {0}. Check the section of the configuration of this endpoint for an entry either for this specific message type or for its assembly.", messageType); - throw new InvalidOperationException(error); - } - return addresses; - } - - /// - /// - /// - public void Unsubscribe() - { - Unsubscribe(typeof(T)); - } - - - /// - /// - /// - public virtual void Unsubscribe(Type messageType) - { - MessagingBestPractices.AssertIsValidForPubSub(messageType, Builder.Build()); - - if (SendOnlyMode) - { - throw new InvalidOperationException("It's not allowed for a send only endpoint to unsubscribe"); - } - - AssertHasLocalAddress(); - - - if (SubscriptionManager == null) - { - throw new InvalidOperationException("No subscription manager is available"); - } - - if (TransportDefinition.HasSupportForCentralizedPubSub) - { - // We are dealing with a brokered transport wired for auto pub/sub. - SubscriptionManager.Unsubscribe(messageType, null); - return; - } - - var addresses = GetAtLeastOneAddressForMessageType(messageType); - - foreach (var destination in addresses) - { - SubscriptionManager.Unsubscribe(messageType, destination); - } - - } - - /// - /// - /// - public void Reply(object message) - { - var options = new ReplyOptions(MessageBeingProcessed.ReplyToAddress, GetCorrelationId()); - - SendMessage(options, LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public void Reply(Action messageConstructor) - { - var instance = messageMapper.CreateInstance(messageConstructor); - var options = new ReplyOptions(MessageBeingProcessed.ReplyToAddress, GetCorrelationId()); - - SendMessage(options, LogicalMessageFactory.Create(instance)); - } - - /// - /// - /// - public void Return(T errorCode) - { - var tType = errorCode.GetType(); - if (!(tType.IsEnum || tType == typeof(Int32) || tType == typeof(Int16) || tType == typeof(Int64))) - { - throw new ArgumentException("The errorCode can only be an enum or an integer.", "errorCode"); - } - - var returnCode = errorCode.ToString(); - if (tType.IsEnum) - { - returnCode = Enum.Format(tType, errorCode, "D"); - } - - var returnMessage = LogicalMessageFactory.CreateControl(new Dictionary - { - {Headers.ReturnMessageErrorCodeHeader, returnCode} - }); - - var options = new ReplyOptions(MessageBeingProcessed.ReplyToAddress, GetCorrelationId()); - - InvokeSendPipeline(options, returnMessage); - } - - string GetCorrelationId() - { - return !string.IsNullOrEmpty(MessageBeingProcessed.CorrelationId) ? MessageBeingProcessed.CorrelationId : MessageBeingProcessed.Id; - } - - /// - /// - /// - public void HandleCurrentMessageLater() - { - if (PipelineFactory.CurrentContext.handleCurrentMessageLaterWasCalled) - { - return; - } - - //if we're a worker, send to the distributor data bus - if (Settings.GetOrDefault("Worker.Enabled")) - { - MessageSender.Send(MessageBeingProcessed, new SendOptions(Settings.Get
("MasterNode.Address"))); - } - else - { - MessageSender.Send(MessageBeingProcessed, new SendOptions(Configure.LocalAddress)); - } - - PipelineFactory.CurrentContext.handleCurrentMessageLaterWasCalled = true; - - ((IncomingContext)PipelineFactory.CurrentContext).DoNotInvokeAnyMoreHandlers(); - } - - /// - /// - /// - public void ForwardCurrentMessageTo(string destination) - { - MessageSender.Send(MessageBeingProcessed, new SendOptions(destination)); - } - - /// - /// - /// - public ICallback SendLocal(Action messageConstructor) - { - return SendLocal(messageMapper.CreateInstance(messageConstructor)); - } - - /// - /// - /// - public ICallback SendLocal(object message) - { - //if we're a worker, send to the distributor data bus - if (Settings.GetOrDefault("Worker.Enabled")) - { - return SendMessage(new SendOptions(Settings.Get
("MasterNode.Address")), LogicalMessageFactory.Create(message)); - } - return SendMessage(new SendOptions(Configure.LocalAddress), LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Send(Action messageConstructor) - { - object message = messageMapper.CreateInstance(messageConstructor); - var destination = GetDestinationForSend(message); - return SendMessage(new SendOptions(destination), LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Send(object message) - { - var destination = GetDestinationForSend(message); - return SendMessage(new SendOptions(destination), LogicalMessageFactory.Create(message)); - } - - Address GetDestinationForSend(object message) - { - var destinations = GetAtLeastOneAddressForMessageType(message.GetType()); - - if (destinations.Count > 1) - { - throw new InvalidOperationException("Sends can only target one address."); - } - - return destinations.SingleOrDefault(); - } - - /// - /// - /// - public ICallback Send(string destination, Action messageConstructor) - { - return SendMessage(new SendOptions(destination), LogicalMessageFactory.Create(messageMapper.CreateInstance(messageConstructor))); - } - - /// - /// - /// - public ICallback Send(Address address, Action messageConstructor) - { - return SendMessage(new SendOptions(address), LogicalMessageFactory.Create(messageMapper.CreateInstance(messageConstructor))); - } - - /// - /// - /// - public ICallback Send(string destination, object message) - { - return SendMessage(new SendOptions(destination), LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Send(Address address, object message) - { - return SendMessage(new SendOptions(address), LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Send(string destination, string correlationId, Action messageConstructor) - { - var options = new SendOptions(destination) - { - CorrelationId = correlationId - }; - - return SendMessage(options, LogicalMessageFactory.Create(messageMapper.CreateInstance(messageConstructor))); - } - - /// - /// - /// - public ICallback Send(Address address, string correlationId, Action messageConstructor) - { - var options = new SendOptions(address) - { - CorrelationId = correlationId - }; - - return SendMessage(options, LogicalMessageFactory.Create(messageMapper.CreateInstance(messageConstructor))); - } - - /// - /// - /// - public ICallback Send(string destination, string correlationId, object message) - { - var options = new SendOptions(destination) - { - CorrelationId = correlationId - }; - - return SendMessage(options, LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Send(Address address, string correlationId, object message) - { - var options = new SendOptions(address) - { - CorrelationId = correlationId - }; - - return SendMessage(options, LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Defer(TimeSpan delay, object message) - { - SendOptions options; - - if (Settings.GetOrDefault("Worker.Enabled")) - { - options = new SendOptions(Settings.Get
("MasterNode.Address")); - } - else - { - options = new SendOptions(Configure.LocalAddress); - } - - options.DelayDeliveryWith = delay; - options.EnforceMessagingBestPractices = false; - - return SendMessage(options, LogicalMessageFactory.Create(message)); - } - - /// - /// - /// - public ICallback Defer(DateTime processAt, object message) - { - SendOptions options; - - if (Settings.GetOrDefault("Worker.Enabled")) - { - options = new SendOptions(Settings.Get
("MasterNode.Address")); - } - else - { - options = new SendOptions(Configure.LocalAddress); - } - - options.DeliverAt = processAt; - options.EnforceMessagingBestPractices = false; - - return SendMessage(options, LogicalMessageFactory.Create(message)); - } - - - ICallback SendMessage(SendOptions sendOptions, LogicalMessage message) - { - var context = InvokeSendPipeline(sendOptions, message); - - var physicalMessage = context.Get(); - - return SetupCallback(physicalMessage.Id); - } - - OutgoingContext InvokeSendPipeline(DeliveryOptions sendOptions, LogicalMessage message) - { - if (sendOptions.ReplyToAddress == null && !SendOnlyMode) - { - sendOptions.ReplyToAddress = Configure.PublicReturnAddress; - } - - if (PropagateReturnAddressOnSend && CurrentMessageContext != null) - { - sendOptions.ReplyToAddress = CurrentMessageContext.ReplyToAddress; - } - - return PipelineFactory.InvokeSendPipeline(sendOptions, message); - } - - - ICallback SetupCallback(string transportMessageId) - { - var result = new NServiceBus.Callback(transportMessageId, Settings.GetOrDefault("Endpoint.SendOnly")); - result.Registered += delegate(object sender, BusAsyncResultEventArgs args) - { - //TODO: what should we do if the key already exists? - messageIdToAsyncResultLookup[args.MessageId] = args.Result; - }; - - return result; - } - - /// - /// - /// - public IBus Start() - { - LicenseManager.PromptUserForLicenseIfTrialHasExpired(); - - if (started) - { - return this; - } - - lock (startLocker) - { - if (started) - { - return this; - } - - AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); - - if (!DoNotStartTransport) - { - Transport.StartedMessageProcessing += TransportStartedMessageProcessing; - Transport.TransportMessageReceived += TransportMessageReceived; - Transport.FinishedMessageProcessing += TransportFinishedMessageProcessing; - Transport.Start(InputAddress); - } - - started = true; - } - - satelliteLauncher = new SatelliteLauncher(Builder); - satelliteLauncher.Start(); - - ProcessStartupItems( - Builder.BuildAll().ToList(), - toRun => - { - toRun.Start(); - thingsRanAtStartup.Add(toRun); - Log.DebugFormat("Started {0}.", toRun.GetType().AssemblyQualifiedName); - }, - ex => CriticalError.Raise("Startup task failed to complete.", ex), - startCompletedEvent); - - return this; - } - - void ExecuteIWantToRunAtStartupStopMethods() - { - Log.Debug("Ensuring IWantToRunWhenBusStartsAndStops.Start has been called."); - startCompletedEvent.WaitOne(); - Log.Debug("All IWantToRunWhenBusStartsAndStops.Start have completed now."); - - var tasksToStop = Interlocked.Exchange(ref thingsRanAtStartup, new ConcurrentBag()); - if (!tasksToStop.Any()) - { - return; - } - - ProcessStartupItems( - tasksToStop, - toRun => - { - toRun.Stop(); - Log.DebugFormat("Stopped {0}.", toRun.GetType().AssemblyQualifiedName); - }, - ex => Log.Fatal("Startup task failed to stop.", ex), - stopCompletedEvent); - - stopCompletedEvent.WaitOne(); - } - - /// - /// Allow disabling the unicast bus. - /// - public bool DoNotStartTransport { get; set; } - - /// - /// The address of this endpoint. - /// - public Address InputAddress { get; set; } - - void AssertHasLocalAddress() - { - if (Configure.LocalAddress == null) - { - throw new InvalidOperationException("Cannot start subscriber without a queue configured. Please specify the LocalAddress property of UnicastBusConfig."); - } - } - - /// - /// - /// - public void Dispose() - { - //Injected at compile time - } - - void DisposeManaged() - { - InnerShutdown(); - Builder.Dispose(); - } - - /// - /// - /// - public void DoNotContinueDispatchingCurrentMessageToHandlers() - { - ((IncomingContext)PipelineFactory.CurrentContext).DoNotInvokeAnyMoreHandlers(); - } - - /// - /// - /// - public IDictionary OutgoingHeaders - { - get - { - return staticOutgoingHeaders; - } - } - - /// - /// - /// - public IMessageContext CurrentMessageContext - { - get - { - TransportMessage current; - - if (!PipelineFactory.CurrentContext.TryGet(IncomingContext.IncomingPhysicalMessageKey, out current)) - { - return null; - } - - return new MessageContext(current); - } - } - - void InnerShutdown() - { - if (!started) - { - return; - } - - Log.Info("Initiating shutdown."); - - if (!DoNotStartTransport) - { - Transport.Stop(); - Transport.StartedMessageProcessing -= TransportStartedMessageProcessing; - Transport.TransportMessageReceived -= TransportMessageReceived; - Transport.FinishedMessageProcessing -= TransportFinishedMessageProcessing; - } - - ExecuteIWantToRunAtStartupStopMethods(); - - satelliteLauncher.Stop(); - - Log.Info("Shutdown complete."); - - started = false; - } - - void TransportStartedMessageProcessing(object sender, StartedMessageProcessingEventArgs e) - { - var incomingMessage = e.Message; - - incomingMessage.Headers[Headers.HostId] = HostInformation.HostId.ToString("N"); - incomingMessage.Headers[Headers.HostDisplayName] = HostInformation.DisplayName; - - PipelineFactory.PreparePhysicalMessagePipelineContext(incomingMessage); - - } - void TransportMessageReceived(object sender, TransportMessageReceivedEventArgs e) - { - PipelineFactory.InvokeReceivePhysicalMessagePipeline(); - } - - void TransportFinishedMessageProcessing(object sender, FinishedMessageProcessingEventArgs e) - { - PipelineFactory.CompletePhysicalMessagePipelineContext(); - } - - /// - /// Gets the destination address For a message type. - /// - /// The message type to get the destination for. - /// The address of the destination associated with the message type. - List
GetAddressForMessageType(Type messageType) - { - var destination = MessageRouter.GetDestinationFor(messageType); - - if (destination.Any()) - { - return destination; - } - - if (messageMapper != null && !messageType.IsInterface) - { - var t = messageMapper.GetMappedTypeFor(messageType); - if (t != null && t != messageType) - { - return GetAddressForMessageType(t); - } - } - - return destination; - } - - /// - /// Map of message identifiers to Async Results - useful for cleanup in case of timeouts. - /// - internal ConcurrentDictionary messageIdToAsyncResultLookup = new ConcurrentDictionary(); - - TransportMessage MessageBeingProcessed - { - get - { - TransportMessage current; - - if (!PipelineFactory.CurrentContext.TryGet(IncomingContext.IncomingPhysicalMessageKey, out current)) - { - throw new InvalidOperationException("There is no current message being processed"); - } - - return current; - } - } - - volatile bool started; - object startLocker = new object(); - - static ILog Log = LogManager.GetLogger(); - - ConcurrentBag thingsRanAtStartup = new ConcurrentBag(); - ManualResetEvent startCompletedEvent = new ManualResetEvent(false); - ManualResetEvent stopCompletedEvent = new ManualResetEvent(true); - - IMessageMapper messageMapper; - SatelliteLauncher satelliteLauncher; - - ConcurrentDictionary staticOutgoingHeaders = new ConcurrentDictionary(); - - - //we need to not inject since at least Autofac doesn't seem to inject internal properties - PipelineExecutor PipelineFactory - { - get - { - return Builder.Build(); - } - } - - LogicalMessageFactory LogicalMessageFactory - { - get - { - return Builder.Build(); - } - } - - TransportDefinition TransportDefinition - { - get - { - return Builder.Build(); - } - } - - static void ProcessStartupItems(IEnumerable items, Action iteration, Action inCaseOfFault, EventWaitHandle eventToSet) - { - eventToSet.Reset(); - - Task.Factory.StartNew(() => - { - Parallel.ForEach(items, iteration); - eventToSet.Set(); - }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness) - .ContinueWith(task => - { - eventToSet.Set(); - inCaseOfFault(task.Exception); - }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.LongRunning); - } - } -} diff --git a/src/NServiceBus.Core/Unicast/UnicastBusConfig.cs b/src/NServiceBus.Core/Unicast/UnicastBusConfig.cs new file mode 100644 index 00000000000..b95515e2e6c --- /dev/null +++ b/src/NServiceBus.Core/Unicast/UnicastBusConfig.cs @@ -0,0 +1,50 @@ +namespace NServiceBus.Config +{ + using System; + using System.Configuration; + + /// + /// A configuration section for UnicastBus specific settings. + /// + public partial class UnicastBusConfig : ConfigurationSection + { + /// + /// Gets/sets the time to be received set on forwarded messages. + /// + [ConfigurationProperty("TimeToBeReceivedOnForwardedMessages", IsRequired = false)] + public TimeSpan TimeToBeReceivedOnForwardedMessages + { + get { return (TimeSpan) this["TimeToBeReceivedOnForwardedMessages"]; } + set { this["TimeToBeReceivedOnForwardedMessages"] = value; } + } + + /// + /// Gets/sets the address that the timeout manager will use to send and receive messages. + /// + [ConfigurationProperty("TimeoutManagerAddress", IsRequired = false)] + public string TimeoutManagerAddress + { + get + { + var result = this["TimeoutManagerAddress"] as string; + if (string.IsNullOrWhiteSpace(result)) + { + result = null; + } + + return result; + } + set { this["TimeoutManagerAddress"] = value; } + } + + /// + /// Contains the mappings from message types (or groups of them) to endpoints. + /// + [ConfigurationProperty("MessageEndpointMappings", IsRequired = false)] + public MessageEndpointMappingCollection MessageEndpointMappings + { + get { return this["MessageEndpointMappings"] as MessageEndpointMappingCollection; } + set { this["MessageEndpointMappings"] = value; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Unicast/UnicastBus_Obsolete.cs b/src/NServiceBus.Core/Unicast/UnicastBus_Obsolete.cs deleted file mode 100644 index e889fa88fe3..00000000000 --- a/src/NServiceBus.Core/Unicast/UnicastBus_Obsolete.cs +++ /dev/null @@ -1,36 +0,0 @@ -#pragma warning disable 1591 -// ReSharper disable UnusedParameter.Global -namespace NServiceBus.Unicast -{ - using System; - - public partial class UnicastBus - { - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "InMemory.Raise has been removed from the core.")] - public void Raise(Action messageConstructor) - { - ThrowInMemoryException(); - } - - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "InMemory.Raise has been removed from the core.")] - public void Raise(T @event) - { - ThrowInMemoryException(); - } - - static void ThrowInMemoryException() - { - throw new Exception("InMemory.Raise has been removed from the core."); - } - - [ObsoleteEx(RemoveInVersion = "6", TreatAsErrorFromVersion = "5", Message = "InMemory has been removed from the core.")] - public IInMemoryOperations InMemory - { - get - { - ThrowInMemoryException(); - return null; - } - } - } -} diff --git a/src/NServiceBus.Core/UnitOfWork/IManageUnitsOfWork.cs b/src/NServiceBus.Core/UnitOfWork/IManageUnitsOfWork.cs index fded0f1ed2d..5a9bf8ede18 100644 --- a/src/NServiceBus.Core/UnitOfWork/IManageUnitsOfWork.cs +++ b/src/NServiceBus.Core/UnitOfWork/IManageUnitsOfWork.cs @@ -1,6 +1,7 @@ namespace NServiceBus.UnitOfWork { using System; + using System.Threading.Tasks; /// /// Interface used by NServiceBus to manage units of work as a part of the @@ -9,13 +10,15 @@ namespace NServiceBus.UnitOfWork public interface IManageUnitsOfWork { /// - /// Called before all message handlers and modules + /// Called before all message handlers and modules. /// - void Begin(); + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task Begin(); /// - /// Called after all message handlers and modules, if an error has occurred the exception will be passed + /// Called after all message handlers and modules, if an error has occurred the exception will be passed. /// - void End(Exception ex = null); + /// This exception will be thrown if null is returned. Return a Task or mark the method as async. + Task End(Exception ex = null); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs new file mode 100644 index 00000000000..cd613c07949 --- /dev/null +++ b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.Features +{ + using System; + using System.Configuration; + using System.Transactions; + using System.Transactions.Configuration; + using ConsistencyGuarantees; + + class TransactionScopeUnitOfWork : Feature + { + protected internal override void Setup(FeatureConfigurationContext context) + { + if (context.Settings.GetRequiredTransactionModeForReceives() == TransportTransactionMode.TransactionScope) + { + throw new Exception("A Transaction scope unit of work can't be used when the transport already uses a scope for the receive operation. Remove the call to config.UnitOfWork().WrapHandlersInATransactionScope() or configure the transport to use a lower transaction mode"); + } + + var transactionOptions = context.Settings.Get().TransactionOptions; + context.Pipeline.Register("HandlerTransactionScopeWrapper", new TransactionScopeUnitOfWorkBehavior(transactionOptions), "Makes sure that the handlers gets wrapped in a transaction scope"); + } + + public class Settings + { + public Settings(TimeSpan? requestedTimeout, IsolationLevel? requestedIsolationLevel) + { + var timeout = TransactionManager.DefaultTimeout; + var isolationLevel = IsolationLevel.ReadCommitted; + if (requestedTimeout.HasValue) + { + var maxTimeout = GetMaxTimeout(); + + if (requestedTimeout.Value > maxTimeout) + { + throw new ConfigurationErrorsException( + "Timeout requested is longer than the maximum value for this machine. Override using the maxTimeout setting of the system.transactions section in machine.config"); + } + + timeout = requestedTimeout.Value; + } + + if (requestedIsolationLevel.HasValue) + { + isolationLevel = requestedIsolationLevel.Value; + } + + TransactionOptions = new TransactionOptions + { + IsolationLevel = isolationLevel, + Timeout = timeout + }; + } + + public TransactionOptions TransactionOptions { get; } + + static TimeSpan GetMaxTimeout() + { + //default is always 10 minutes + var maxTimeout = TimeSpan.FromMinutes(10); + + var systemTransactionsGroup = ConfigurationManager.OpenMachineConfiguration() + .GetSectionGroup("system.transactions"); + + var machineSettings = systemTransactionsGroup?.Sections.Get("machineSettings") as MachineSettingsSection; + + if (machineSettings != null) + { + maxTimeout = machineSettings.MaxTimeout; + } + + return maxTimeout; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs new file mode 100644 index 00000000000..86dc3d1a0a3 --- /dev/null +++ b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs @@ -0,0 +1,32 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using Pipeline; + + class TransactionScopeUnitOfWorkBehavior : IBehavior + { + public TransactionScopeUnitOfWorkBehavior(TransactionOptions transactionOptions) + { + this.transactionOptions = transactionOptions; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + if (Transaction.Current != null) + { + throw new Exception("Ambient transaction detected. The transaction scope unit of work is not supported when there already is a scope present."); + } + + using (var tx = new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled)) + { + await next(context).ConfigureAwait(false); + + tx.Complete(); + } + } + + TransactionOptions transactionOptions; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs index 058ec61660f..a85b8a70c9d 100644 --- a/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs +++ b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs @@ -3,28 +3,34 @@ using System; using System.Collections.Generic; using System.Linq; - using NServiceBus.UnitOfWork; + using System.Threading.Tasks; using Pipeline; - using Pipeline.Contexts; + using UnitOfWork; - - class UnitOfWorkBehavior : IBehavior + class UnitOfWorkBehavior : IBehavior { - public void Invoke(IncomingContext context, Action next) + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) { + var unitsOfWork = new Stack(); + try { foreach (var uow in context.Builder.BuildAll()) { unitsOfWork.Push(uow); - uow.Begin(); + await uow.Begin() + .ThrowIfNull() + .ConfigureAwait(false); } - next(); + await next(context).ConfigureAwait(false); while (unitsOfWork.Count > 0) { - unitsOfWork.Pop().End(); + var popped = unitsOfWork.Pop(); + await popped.End() + .ThrowIfNull() + .ConfigureAwait(false); } } catch (MessageDeserializationException) @@ -33,7 +39,7 @@ public void Invoke(IncomingContext context, Action next) } catch (Exception exception) { - var trailingExceptions = AppendEndExceptionsAndRethrow(exception); + var trailingExceptions = await AppendEndExceptions(unitsOfWork, exception).ConfigureAwait(false); if (trailingExceptions.Any()) { trailingExceptions.Insert(0, exception); @@ -43,7 +49,7 @@ public void Invoke(IncomingContext context, Action next) } } - List AppendEndExceptionsAndRethrow(Exception initialException) + static async Task> AppendEndExceptions(Stack unitsOfWork, Exception initialException) { var exceptionsToThrow = new List(); while (unitsOfWork.Count > 0) @@ -51,7 +57,9 @@ List AppendEndExceptionsAndRethrow(Exception initialException) var uow = unitsOfWork.Pop(); try { - uow.End(initialException); + await uow.End(initialException) + .ThrowIfNull() + .ConfigureAwait(false); } catch (Exception endException) { @@ -60,8 +68,5 @@ List AppendEndExceptionsAndRethrow(Exception initialException) } return exceptionsToThrow; } - - Stack unitsOfWork = new Stack(); - } } \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettings.cs b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettings.cs new file mode 100644 index 00000000000..4f7d7245ce5 --- /dev/null +++ b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettings.cs @@ -0,0 +1,30 @@ +namespace NServiceBus +{ + using System; + using System.Transactions; + using Features; + + /// + /// Configuration class for Unit Of Work settings. + /// + public class UnitOfWorkSettings + { + internal UnitOfWorkSettings(EndpointConfiguration config) + { + this.config = config; + } + + /// + /// Wraps handlers in a to make sure all storage + /// operations becomes part of the same unit of work. + /// + public UnitOfWorkSettings WrapHandlersInATransactionScope(TimeSpan? timeout = null, IsolationLevel? isolationLevel = null) + { + config.EnableFeature(); + config.Settings.Set(new TransactionScopeUnitOfWork.Settings(timeout, isolationLevel)); + return this; + } + + EndpointConfiguration config; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettingsExtensions.cs b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettingsExtensions.cs new file mode 100644 index 00000000000..ff8ae0b00a6 --- /dev/null +++ b/src/NServiceBus.Core/UnitOfWork/UnitOfWorkSettingsExtensions.cs @@ -0,0 +1,18 @@ +namespace NServiceBus +{ + /// + /// Provides configuration options for unit of work behavior. + /// + public static class UnitOfWorkSettingsExtensions + { + /// + /// Entry point for unit of work related configuration. + /// + /// The instance to apply the settings to. + public static UnitOfWorkSettings UnitOfWork(this EndpointConfiguration config) + { + Guard.AgainstNull(nameof(config), config); + return new UnitOfWorkSettings(config); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/AsyncTimer.cs b/src/NServiceBus.Core/Utils/AsyncTimer.cs new file mode 100644 index 00000000000..000b382b9fa --- /dev/null +++ b/src/NServiceBus.Core/Utils/AsyncTimer.cs @@ -0,0 +1,51 @@ +namespace NServiceBus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + class AsyncTimer : IAsyncTimer + { + public void Start(Func callback, TimeSpan interval, Action errorCallback) + { + tokenSource = new CancellationTokenSource(); + var token = tokenSource.Token; + + task = Task.Run(async () => + { + while (!token.IsCancellationRequested) + { + try + { + await Task.Delay(interval, token).ConfigureAwait(false); + await callback().ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // nop + } + catch (Exception ex) + { + errorCallback(ex); + } + } + }, CancellationToken.None); + } + + public Task Stop() + { + if (tokenSource == null) + { + return TaskEx.CompletedTask; + } + + tokenSource.Cancel(); + tokenSource.Dispose(); + + return task ?? TaskEx.CompletedTask; + } + + Task task; + CancellationTokenSource tokenSource; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/BaseDictionary.cs b/src/NServiceBus.Core/Utils/BaseDictionary.cs deleted file mode 100644 index a9186d149ec..00000000000 --- a/src/NServiceBus.Core/Utils/BaseDictionary.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace NServiceBus.Utils -{ - using System.Collections; - using System.Diagnostics; - - [DebuggerDisplay("Count = {Count}")] - [DebuggerTypeProxy(PREFIX + "DictionaryDebugView`2" + SUFFIX)] - internal abstract class BaseDictionary : IDictionary - { - private const string PREFIX = "System.Collections.Generic.Mscorlib_"; - private const string SUFFIX = ",mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"; - - private KeyCollection keys; - private ValueCollection values; - - public abstract int Count { get; } - public abstract void Clear(); - public abstract void Add(TKey key, TValue value); - public abstract bool ContainsKey(TKey key); - public abstract bool Remove(TKey key); - public abstract bool TryGetValue(TKey key, out TValue value); - public abstract IEnumerator> GetEnumerator(); - protected abstract void SetValue(TKey key, TValue value); - - public bool IsReadOnly - { - get { return false; } - } - - public ICollection Keys - { - get - { - if (keys == null) - keys = new KeyCollection(this); - - return keys; - } - } - - public ICollection Values - { - get - { - if (values == null) - values = new ValueCollection(this); - - return values; - } - } - - public TValue this[TKey key] - { - get - { - TValue value; - if (!TryGetValue(key, out value)) - throw new KeyNotFoundException(); - - return value; - } - set - { - SetValue(key, value); - } - } - - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - public bool Contains(KeyValuePair item) - { - TValue value; - if (!TryGetValue(item.Key, out value)) - return false; - - return EqualityComparer.Default.Equals(value, item.Value); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - Copy(this, array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - if (!Contains(item)) - return false; - - return Remove(item.Key); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private abstract class Collection : ICollection - { - protected readonly IDictionary dictionary; - - protected Collection(IDictionary dictionary) - { - this.dictionary = dictionary; - } - - public int Count - { - get { return dictionary.Count; } - } - - public bool IsReadOnly - { - get { return true; } - } - - public void CopyTo(T[] array, int arrayIndex) - { - Copy(this, array, arrayIndex); - } - - public virtual bool Contains(T item) - { - foreach (var element in this) - if (EqualityComparer.Default.Equals(element, item)) - return true; - return false; - } - - public IEnumerator GetEnumerator() - { - foreach (var pair in dictionary) - yield return GetItem(pair); - } - - protected abstract T GetItem(KeyValuePair pair); - - public bool Remove(T item) - { - throw new NotSupportedException("Collection is read-only."); - } - - public void Add(T item) - { - throw new NotSupportedException("Collection is read-only."); - } - - public void Clear() - { - throw new NotSupportedException("Collection is read-only."); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - [DebuggerDisplay("Count = {Count}")] - [DebuggerTypeProxy(PREFIX + "DictionaryKeyCollectionDebugView`2" + SUFFIX)] - private class KeyCollection : Collection - { - public KeyCollection(IDictionary dictionary) - : base(dictionary) { } - - protected override TKey GetItem(KeyValuePair pair) - { - return pair.Key; - } - public override bool Contains(TKey item) - { - return dictionary.ContainsKey(item); - } - } - - [DebuggerDisplay("Count = {Count}")] - [DebuggerTypeProxy(PREFIX + "DictionaryValueCollectionDebugView`2" + SUFFIX)] - private class ValueCollection : Collection - { - public ValueCollection(IDictionary dictionary) - : base(dictionary) { } - - protected override TValue GetItem(KeyValuePair pair) - { - return pair.Value; - } - } - - private static void Copy(ICollection source, T[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException("array"); - - if (arrayIndex < 0 || arrayIndex > array.Length) - throw new ArgumentOutOfRangeException("arrayIndex"); - - if ((array.Length - arrayIndex) < source.Count) - throw new ArgumentException("Destination array is not large enough. Check array.Length and arrayIndex."); - - foreach (var item in source) - array[arrayIndex++] = item; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/DeterministicGuid.cs b/src/NServiceBus.Core/Utils/DeterministicGuid.cs index e062b2b9a17..159319e7b22 100644 --- a/src/NServiceBus.Core/Utils/DeterministicGuid.cs +++ b/src/NServiceBus.Core/Utils/DeterministicGuid.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Utils +namespace NServiceBus { using System; using System.Security.Cryptography; @@ -11,11 +11,11 @@ public static Guid Create(params object[] data) // use MD5 hash to get a 16-byte hash of the string using (var provider = new MD5CryptoServiceProvider()) { - var inputBytes = Encoding.Default.GetBytes(String.Concat(data)); + var inputBytes = Encoding.Default.GetBytes(string.Concat(data)); var hashBytes = provider.ComputeHash(inputBytes); // generate a guid from the hash: return new Guid(hashBytes); } - } + } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/ElevateChecker.cs b/src/NServiceBus.Core/Utils/ElevateChecker.cs index abb0f6e8bc2..6cf7447b18e 100644 --- a/src/NServiceBus.Core/Utils/ElevateChecker.cs +++ b/src/NServiceBus.Core/Utils/ElevateChecker.cs @@ -1,18 +1,13 @@ -namespace NServiceBus.Installation +namespace NServiceBus { using System.Security.Principal; static class ElevateChecker { - public static bool IsCurrentUserElevated() { using (var windowsIdentity = WindowsIdentity.GetCurrent()) { - if (windowsIdentity == null) - { - return false; - } var windowsPrincipal = new WindowsPrincipal(windowsIdentity); return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); } diff --git a/src/NServiceBus.Core/Utils/ExceptionHeaderHelper.cs b/src/NServiceBus.Core/Utils/ExceptionHeaderHelper.cs index bc864adf4da..8569fa99ae8 100644 --- a/src/NServiceBus.Core/Utils/ExceptionHeaderHelper.cs +++ b/src/NServiceBus.Core/Utils/ExceptionHeaderHelper.cs @@ -3,25 +3,11 @@ using System; using System.Collections; using System.Collections.Generic; - using System.Configuration; - using NServiceBus.Faults; static class ExceptionHeaderHelper { - static bool useLegacyStackTrace = String.Equals(ConfigurationManager.AppSettings["NServiceBus/Headers/UseLegacyExceptionStackTrace"], "true", StringComparison.OrdinalIgnoreCase); - - public static void SetExceptionHeaders(this TransportMessage message,Exception e, Address failedQueue, string reason = null) - { - var headers = message.Headers; - SetExceptionHeaders(headers, e, failedQueue, reason, useLegacyStackTrace); - } - - internal static void SetExceptionHeaders(Dictionary headers, Exception e, Address failedQueue, string reason, bool legacyStackTrace) + public static void SetExceptionHeaders(Dictionary headers, Exception e) { - if (!string.IsNullOrWhiteSpace(reason)) - { - headers["NServiceBus.ExceptionInfo.Reason"] = reason; - } headers["NServiceBus.ExceptionInfo.ExceptionType"] = e.GetType().FullName; if (e.InnerException != null) @@ -31,27 +17,24 @@ internal static void SetExceptionHeaders(Dictionary headers, Exc headers["NServiceBus.ExceptionInfo.HelpLink"] = e.HelpLink; headers["NServiceBus.ExceptionInfo.Message"] = e.GetMessage().Truncate(16384); - headers["NServiceBus.ExceptionInfo.Source"] = e.Source; - if (legacyStackTrace) - { - headers["NServiceBus.ExceptionInfo.StackTrace"] = e.StackTrace; - } - else - { - headers["NServiceBus.ExceptionInfo.StackTrace"] = e.ToString(); - } - headers[FaultsHeaderKeys.FailedQ] = failedQueue.ToString(); + headers["NServiceBus.ExceptionInfo.Source"] = e.Source; + headers["NServiceBus.ExceptionInfo.StackTrace"] = e.ToString(); headers["NServiceBus.TimeOfFailure"] = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow); -// ReSharper disable once ConditionIsAlwaysTrueOrFalse - if(e.Data == null) -// ReSharper disable once HeuristicUnreachableCode + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (e.Data == null) + // ReSharper disable HeuristicUnreachableCode + { return; + } + // ReSharper restore HeuristicUnreachableCode foreach (DictionaryEntry entry in e.Data) { if (entry.Value == null) + { continue; + } headers["NServiceBus.ExceptionInfo.Data." + entry.Key] = entry.Value.ToString(); } } @@ -63,4 +46,4 @@ static string Truncate(this string value, int maxLength) => ? value : value.Substring(0, maxLength)); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/FileVersionRetriever.cs b/src/NServiceBus.Core/Utils/FileVersionRetriever.cs index 2041e2bb760..f0efc1e789a 100644 --- a/src/NServiceBus.Core/Utils/FileVersionRetriever.cs +++ b/src/NServiceBus.Core/Utils/FileVersionRetriever.cs @@ -10,13 +10,13 @@ class FileVersionRetriever { /// - /// Retrieves a semver compliant version from a . + /// Retrieves a semver compliant version from a . /// - /// to retrieve version from. + /// to retrieve version from. /// SemVer compliant version. public static string GetFileVersion(Type type) { - if (!String.IsNullOrEmpty(type.Assembly.Location)) + if (!string.IsNullOrEmpty(type.Assembly.Location)) { var fileVersion = FileVersionInfo.GetVersionInfo(type.Assembly.Location); @@ -27,7 +27,7 @@ public static string GetFileVersion(Type type) if (customAttributes.Length >= 1) { - var fileVersion = (AssemblyFileVersionAttribute)customAttributes[0]; + var fileVersion = (AssemblyFileVersionAttribute) customAttributes[0]; Version version; if (Version.TryParse(fileVersion.Version, out version)) { @@ -38,4 +38,4 @@ public static string GetFileVersion(Type type) return type.Assembly.GetName().Version.ToString(3); } } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/Guard.cs b/src/NServiceBus.Core/Utils/Guard.cs index 4d1395f878a..c893a52008a 100644 --- a/src/NServiceBus.Core/Utils/Guard.cs +++ b/src/NServiceBus.Core/Utils/Guard.cs @@ -1,16 +1,85 @@ -namespace NServiceBus.Utils +namespace NServiceBus { using System; + using System.Collections; using System.Linq; using System.Reflection; + using JetBrains.Annotations; static class Guard { - public static void TypeHasDefaultConstructor(Type type, string argumentName) +// ReSharper disable UnusedParameter.Global + public static void TypeHasDefaultConstructor(Type type, [InvokerParameterName] string argumentName) { if (type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .All(ctor => ctor.GetParameters().Length != 0)) - throw new ArgumentException(String.Format("Type '{0}' must have a default constructor.", type.FullName), argumentName); + .All(ctor => ctor.GetParameters().Length != 0)) + { + var error = $"Type '{type.FullName}' must have a default constructor."; + throw new ArgumentException(error, argumentName); + } + } + + [ContractAnnotation("value: null => halt")] + public static void AgainstNull([InvokerParameterName] string argumentName, [NotNull] object value) + { + if (value == null) + { + throw new ArgumentNullException(argumentName); + } + } + + [ContractAnnotation("value: null => halt")] + public static void AgainstNullAndEmpty([InvokerParameterName] string argumentName, [NotNull] string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentNullException(argumentName); + } + } + + [ContractAnnotation("value: null => halt")] + public static void AgainstNullAndEmpty([InvokerParameterName] string argumentName, [NotNull, NoEnumeration] ICollection value) + { + if (value == null) + { + throw new ArgumentNullException(argumentName); + } + if (value.Count == 0) + { + throw new ArgumentOutOfRangeException(argumentName); + } + } + + public static void AgainstNegativeAndZero([InvokerParameterName] string argumentName, int value) + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(argumentName); + } + } + + public static void AgainstNegative([InvokerParameterName] string argumentName, int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(argumentName); + } + } + + public static void AgainstNegativeAndZero([InvokerParameterName] string argumentName, TimeSpan value) + { + if (value <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(argumentName); + } + } + + public static void AgainstNegative([InvokerParameterName] string argumentName, TimeSpan value) + { + if (value < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(argumentName); + } } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/IAsyncTimer.cs b/src/NServiceBus.Core/Utils/IAsyncTimer.cs new file mode 100644 index 00000000000..d6e14f33073 --- /dev/null +++ b/src/NServiceBus.Core/Utils/IAsyncTimer.cs @@ -0,0 +1,11 @@ +namespace NServiceBus +{ + using System; + using System.Threading.Tasks; + + interface IAsyncTimer + { + void Start(Func callback, TimeSpan interval, Action errorCallback); + Task Stop(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/MessageQueueExtensions.cs b/src/NServiceBus.Core/Utils/MessageQueueExtensions.cs new file mode 100644 index 00000000000..a04c1070dd7 --- /dev/null +++ b/src/NServiceBus.Core/Utils/MessageQueueExtensions.cs @@ -0,0 +1,195 @@ +namespace NServiceBus +{ + using System; + using System.ComponentModel; + using System.Messaging; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// + /// Reads the Access Control Entries (ACE) from an MSMQ queue. + /// + /// + /// There is no managed API for reading the queue permissions, this has to be done via P/Invoke. by calling + /// MQGetQueueSecurity API. + /// See http://stackoverflow.com/questions/10177255/how-to-get-the-current-permissions-for-an-msmq-private-queue + /// + static class MessageQueueExtensions + { + [DllImport("mqrt.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern int MQGetQueueSecurity(string formatName, int SecurityInformation, IntPtr SecurityDescriptor, int length, out int lengthNeeded); + + [DllImport("advapi32.dll", SetLastError = true)] + static extern bool GetSecurityDescriptorDacl(IntPtr pSD, out bool daclPresent, out IntPtr pDacl, out bool daclDefaulted); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern bool GetAclInformation(IntPtr pAcl, ref ACL_SIZE_INFORMATION pAclInformation, uint nAclInformationLength, ACL_INFORMATION_CLASS dwAclInformationClass); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern int GetAce(IntPtr aclPtr, int aceIndex, out IntPtr acePtr); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern int GetLengthSid(IntPtr pSID); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern bool ConvertSidToStringSid([MarshalAs(UnmanagedType.LPArray)] byte[] pSID, out IntPtr ptrSid); + + const string PREFIX_FORMAT_NAME = "FORMATNAME:"; + const int DACL_SECURITY_INFORMATION = 4; + const int MQ_ERROR_SECURITY_DESCRIPTOR_TOO_SMALL = unchecked((int) 0xc00e0023); + const int MQ_OK = 0; + static bool administerGranted; + + //Security constants + + // the following constants taken from MessageQueue.cs (see http://referencesource.microsoft.com/#System.Messaging/System/Messaging/MessageQueue.cs) + // ReSharper disable MemberCanBePrivate.Local + [StructLayout(LayoutKind.Sequential)] + struct ACE_HEADER + { + public byte AceType; + public byte AceFlags; + public short AceSize; + } + + [StructLayout(LayoutKind.Sequential)] + struct ACCESS_ALLOWED_ACE + { + public ACE_HEADER Header; + public uint Mask; + public int SidStart; + } + + [StructLayout(LayoutKind.Sequential)] + struct ACL_SIZE_INFORMATION + { + public uint AceCount; + public uint AclBytesInUse; + public uint AclBytesFree; + } + + enum ACL_INFORMATION_CLASS + { + // ReSharper disable once UnusedMember.Local + AclRevisionInformation = 1, + AclSizeInformation + } + + public static bool TryGetPermissions(this MessageQueue queue, string user, out MessageQueueAccessRights? rights) + { + if (!administerGranted) + { + var permission = new MessageQueuePermission(MessageQueuePermissionAccess.Administer, PREFIX_FORMAT_NAME + queue.FormatName); + permission.Demand(); + + administerGranted = true; + } + + var sid = GetSidForUser(user); + + try + { + rights = GetPermissions(queue.FormatName, sid); + return true; + } + catch + { + rights = null; + return false; + } + } + + static MessageQueueAccessRights GetPermissions(string formatName, string sid) + { + var SecurityDescriptor = new byte[100]; + + var sdHandle = GCHandle.Alloc(SecurityDescriptor, GCHandleType.Pinned); + try + { + int lengthNeeded; + var mqResult = MQGetQueueSecurity(formatName, + DACL_SECURITY_INFORMATION, + sdHandle.AddrOfPinnedObject(), + SecurityDescriptor.Length, + out lengthNeeded); + + if (mqResult == MQ_ERROR_SECURITY_DESCRIPTOR_TOO_SMALL) + { + sdHandle.Free(); + SecurityDescriptor = new byte[lengthNeeded]; + sdHandle = GCHandle.Alloc(SecurityDescriptor, GCHandleType.Pinned); + mqResult = MQGetQueueSecurity(formatName, + DACL_SECURITY_INFORMATION, + sdHandle.AddrOfPinnedObject(), + SecurityDescriptor.Length, + out lengthNeeded); + } + + if (mqResult != MQ_OK) + { + throw new Exception($"Unable to read the security descriptor of queue [{formatName}]"); + } + + bool daclPresent, daclDefaulted; + IntPtr pDacl; + var success = GetSecurityDescriptorDacl(sdHandle.AddrOfPinnedObject(), + out daclPresent, + out pDacl, + out daclDefaulted); + + if (!success) + { + throw new Win32Exception(); + } + + var allowedAce = GetAce(pDacl, sid); + + return (MessageQueueAccessRights) allowedAce.Mask; + } + finally + { + if (sdHandle.IsAllocated) + { + sdHandle.Free(); + } + } + } + + static string GetSidForUser(string username) + { + var account = new NTAccount(username); + var sid = (SecurityIdentifier) account.Translate(typeof(SecurityIdentifier)); + + return sid.ToString(); + } + + static ACCESS_ALLOWED_ACE GetAce(IntPtr pDacl, string sid) + { + var AclSize = new ACL_SIZE_INFORMATION(); + GetAclInformation(pDacl, ref AclSize, (uint) Marshal.SizeOf(typeof(ACL_SIZE_INFORMATION)), ACL_INFORMATION_CLASS.AclSizeInformation); + + for (var i = 0; i < AclSize.AceCount; i++) + { + IntPtr pAce; + GetAce(pDacl, i, out pAce); + var ace = (ACCESS_ALLOWED_ACE) Marshal.PtrToStructure(pAce, typeof(ACCESS_ALLOWED_ACE)); + + var iter = (IntPtr) ((long) pAce + (long) Marshal.OffsetOf(typeof(ACCESS_ALLOWED_ACE), "SidStart")); + var size = GetLengthSid(iter); + var bSID = new byte[size]; + Marshal.Copy(iter, bSID, 0, size); + IntPtr ptrSid; + ConvertSidToStringSid(bSID, out ptrSid); + + var strSID = Marshal.PtrToStringAuto(ptrSid); + + if (strSID == sid) + { + return ace; + } + } + + throw new Exception($"No ACE for SID {sid} found in security descriptor"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/PathUtilities.cs b/src/NServiceBus.Core/Utils/PathUtilities.cs new file mode 100644 index 00000000000..2f2363fa5d5 --- /dev/null +++ b/src/NServiceBus.Core/Utils/PathUtilities.cs @@ -0,0 +1,19 @@ +namespace NServiceBus +{ + using System.Linq; + using System.Text.RegularExpressions; + + static class PathUtilities + { + public static string SanitizedPath(string commandLine) + { + if (commandLine.StartsWith("\"")) + { + return (from Match match in Regex.Matches(commandLine, "\"([^\"]*)\"") + select match.ToString()).First().Trim('"'); + } + + return commandLine.Split(' ').First(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/Reflection/DelegateFactory.cs b/src/NServiceBus.Core/Utils/Reflection/DelegateFactory.cs index 3e085efe98b..5a97f906430 100644 --- a/src/NServiceBus.Core/Utils/Reflection/DelegateFactory.cs +++ b/src/NServiceBus.Core/Utils/Reflection/DelegateFactory.cs @@ -1,18 +1,13 @@ -namespace NServiceBus.Utils.Reflection +namespace NServiceBus { using System; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; - - static class DelegateFactory - { - static readonly ConcurrentDictionary> PropertyInfoToLateBoundProperty = new ConcurrentDictionary>(); - static readonly ConcurrentDictionary> FieldInfoToLateBoundField = new ConcurrentDictionary>(); - static readonly ConcurrentDictionary> PropertyInfoToLateBoundPropertySet = new ConcurrentDictionary>(); - static readonly ConcurrentDictionary> FieldInfoToLateBoundFieldSet = new ConcurrentDictionary>(); + static class DelegateFactory + { public static Func CreateGet(PropertyInfo property) { Func lateBoundPropertyGet; @@ -64,7 +59,11 @@ public static Action CreateSet(FieldInfo field) if (!FieldInfoToLateBoundFieldSet.TryGetValue(field, out callback)) { var sourceType = field.DeclaringType; - var method = new DynamicMethod("Set" + field.Name, null, new[] { typeof(object), typeof(object) }, true); + var method = new DynamicMethod("Set" + field.Name, null, new[] + { + typeof(object), + typeof(object) + }, true); var gen = method.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); // Load input to stack @@ -74,7 +73,7 @@ public static Action CreateSet(FieldInfo field) gen.Emit(OpCodes.Stfld, field); // Set the value to the input field gen.Emit(OpCodes.Ret); - callback = (Action)method.CreateDelegate(typeof(Action)); + callback = (Action) method.CreateDelegate(typeof(Action)); FieldInfoToLateBoundFieldSet[field] = callback; } @@ -87,7 +86,11 @@ public static Action CreateSet(PropertyInfo property) if (!PropertyInfoToLateBoundPropertySet.TryGetValue(property, out result)) { - var method = new DynamicMethod("Set" + property.Name, null, new[] { typeof(object), typeof(object) }, true); + var method = new DynamicMethod("Set" + property.Name, null, new[] + { + typeof(object), + typeof(object) + }, true); var gen = method.GetILGenerator(); var sourceType = property.DeclaringType; @@ -100,12 +103,16 @@ public static Action CreateSet(PropertyInfo property) gen.Emit(OpCodes.Callvirt, setter); // Call the setter method gen.Emit(OpCodes.Ret); - result = (Action)method.CreateDelegate(typeof(Action)); + result = (Action) method.CreateDelegate(typeof(Action)); PropertyInfoToLateBoundPropertySet[property] = result; } return result; } + static ConcurrentDictionary> PropertyInfoToLateBoundProperty = new ConcurrentDictionary>(); + static ConcurrentDictionary> FieldInfoToLateBoundField = new ConcurrentDictionary>(); + static ConcurrentDictionary> PropertyInfoToLateBoundPropertySet = new ConcurrentDictionary>(); + static ConcurrentDictionary> FieldInfoToLateBoundFieldSet = new ConcurrentDictionary>(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/Reflection/ExtensionMethods.cs b/src/NServiceBus.Core/Utils/Reflection/ExtensionMethods.cs index 36a82601ed4..03a108ac539 100644 --- a/src/NServiceBus.Core/Utils/Reflection/ExtensionMethods.cs +++ b/src/NServiceBus.Core/Utils/Reflection/ExtensionMethods.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Utils.Reflection +namespace NServiceBus { using System; using System.Collections; @@ -7,19 +7,19 @@ namespace NServiceBus.Utils.Reflection using System.Linq; using System.Reflection; - static class ExtensionMethods + static class TypeExtensionMethods { - - public static T Construct(this Type type) { - var defaultConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { }, null); + var defaultConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] + { + }, null); if (defaultConstructor != null) { - return (T)defaultConstructor.Invoke(null); + return (T) defaultConstructor.Invoke(null); } - return (T)Activator.CreateInstance(type); + return (T) Activator.CreateInstance(type); } /// @@ -27,14 +27,14 @@ public static T Construct(this Type type) /// public static bool IsSimpleType(this Type type) { - return (type == typeof(string) || - type.IsPrimitive || - type == typeof(decimal) || - type == typeof(Guid) || - type == typeof(DateTime) || - type == typeof(TimeSpan) || - type == typeof(DateTimeOffset) || - type.IsEnum); + return type == typeof(string) || + type.IsPrimitive || + type == typeof(decimal) || + type == typeof(Guid) || + type == typeof(DateTime) || + type == typeof(TimeSpan) || + type == typeof(DateTimeOffset) || + type.IsEnum; } public static bool IsNullableType(this Type type) @@ -53,7 +53,7 @@ public static bool IsNullableType(this Type type) /// public static string SerializationFriendlyName(this Type t) { - return TypeToNameLookup.GetOrAdd(t, type => + return TypeToNameLookup.GetOrAdd(t.TypeHandle, typeHandle => { var index = t.Name.IndexOf('`'); if (index >= 0) @@ -79,49 +79,42 @@ public static string SerializationFriendlyName(this Type t) return result; } - return type.Name; + return Type.GetTypeFromHandle(typeHandle).Name; }); } - static byte[] MsPublicKeyToken = typeof(string).Assembly.GetName().GetPublicKeyToken(); - static bool IsClrType(byte[] a1) { IStructuralEquatable structuralEquatable = a1; return structuralEquatable.Equals(MsPublicKeyToken, StructuralComparisons.StructuralEqualityComparer); } - static ConcurrentDictionary IsSystemTypeCache = new ConcurrentDictionary(); - public static bool IsSystemType(this Type type) { bool result; - if (!IsSystemTypeCache.TryGetValue(type, out result)) + if (!IsSystemTypeCache.TryGetValue(type.TypeHandle, out result)) { var nameOfContainingAssembly = type.Assembly.GetName().GetPublicKeyToken(); - IsSystemTypeCache[type] = result = IsClrType(nameOfContainingAssembly); + IsSystemTypeCache[type.TypeHandle] = result = IsClrType(nameOfContainingAssembly); } return result; } - public static bool IsNServiceBusMarkerInterface(this Type type) - { - return type == typeof(IMessage) || - type == typeof(ICommand) || - type == typeof(IEvent); - } - - static ConcurrentDictionary TypeToNameLookup = new ConcurrentDictionary(); - - static byte[] nsbPublicKeyToken = typeof(ExtensionMethods).Assembly.GetName().GetPublicKeyToken(); - public static bool IsFromParticularAssembly(this Type type) { return type.Assembly.GetName() .GetPublicKeyToken() .SequenceEqual(nsbPublicKeyToken); } + + static byte[] MsPublicKeyToken = typeof(string).Assembly.GetName().GetPublicKeyToken(); + + static ConcurrentDictionary IsSystemTypeCache = new ConcurrentDictionary(); + + static ConcurrentDictionary TypeToNameLookup = new ConcurrentDictionary(); + + static byte[] nsbPublicKeyToken = typeof(TypeExtensionMethods).Assembly.GetName().GetPublicKeyToken(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/Reflection/Reflect.cs b/src/NServiceBus.Core/Utils/Reflection/Reflect.cs index 4b93f81abc0..d5e942c6ba5 100644 --- a/src/NServiceBus.Core/Utils/Reflection/Reflect.cs +++ b/src/NServiceBus.Core/Utils/Reflection/Reflect.cs @@ -1,6 +1,6 @@ //http://netfx.googlecode.com/svn/trunk/Source/Reflection/Reflect.cs -namespace NServiceBus.Utils.Reflection +namespace NServiceBus { using System; using System.Collections.Generic; @@ -8,12 +8,15 @@ namespace NServiceBus.Utils.Reflection using System.Linq.Expressions; using System.Reflection; - static class Reflect + static class Reflect { public static PropertyInfo GetProperty(Expression> property) { var info = GetMemberInfo(property, false) as PropertyInfo; - if (info == null) throw new ArgumentException("Member is not a property"); + if (info == null) + { + throw new ArgumentException("Member is not a property"); + } return info; } @@ -30,12 +33,18 @@ public static PropertyInfo GetProperty(Expression> propert return GetMemberInfo(property, checkForSingleDot) as PropertyInfo; } - static MemberInfo GetMemberInfo(Expression member, bool checkForSingleDot) + public static MemberInfo GetMemberInfo(Expression member, bool checkForSingleDot) { - if (member == null) throw new ArgumentNullException("member"); + if (member == null) + { + throw new ArgumentNullException(nameof(member)); + } var lambda = member as LambdaExpression; - if (lambda == null) throw new ArgumentException("Not a lambda expression", "member"); + if (lambda == null) + { + throw new ArgumentException("Not a lambda expression", nameof(member)); + } MemberExpression memberExpr = null; @@ -45,14 +54,17 @@ static MemberInfo GetMemberInfo(Expression member, bool checkForSingleDot) { // The cast is an unary expression, where the operand is the // actual member access expression. - memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression; + memberExpr = ((UnaryExpression) lambda.Body).Operand as MemberExpression; } else if (lambda.Body.NodeType == ExpressionType.MemberAccess) { memberExpr = lambda.Body as MemberExpression; } - if (memberExpr == null) throw new ArgumentException("Not a member access", "member"); + if (memberExpr == null) + { + throw new ArgumentException("Not a member access", nameof(member)); + } if (checkForSingleDot) { @@ -60,11 +72,10 @@ static MemberInfo GetMemberInfo(Expression member, bool checkForSingleDot) { return memberExpr.Member; } - throw new ArgumentException("Argument passed contains more than a single dot which is not allowed: " + member, "member"); + throw new ArgumentException("Argument passed contains more than a single dot which is not allowed: " + member, nameof(member)); } return memberExpr.Member; } - } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Utils/RegistryReader.cs b/src/NServiceBus.Core/Utils/RegistryReader.cs index 23ad7b00d5d..2f244d3c67e 100644 --- a/src/NServiceBus.Core/Utils/RegistryReader.cs +++ b/src/NServiceBus.Core/Utils/RegistryReader.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Utils +namespace NServiceBus { using System; using Logging; @@ -6,17 +6,15 @@ static class RegistryReader { - static ILog Logger = LogManager.GetLogger(typeof(RegistryReader)); - public static string Read(string name, string defaultValue = null) { try - { + { return ReadRegistryKeyValue(name, defaultValue); } catch (Exception ex) { - Logger.Warn(string.Format(@"We couldn't read the registry to retrieve the {0}, from 'HKEY_LOCAL_MACHINE\SOFTWARE\ParticularSoftware\ServiceBus'.", name), ex); + Logger.Warn($@"Could not read the registry to retrieve the {name}, from 'HKEY_LOCAL_MACHINE\SOFTWARE\ParticularSoftware\ServiceBus'.", ex); } return defaultValue; @@ -42,5 +40,7 @@ static string ReadRegistry(RegistryView view, string keyName, string defaultValu return (string) key.GetValue(keyName, defaultValue); } } + + static ILog Logger = LogManager.GetLogger(typeof(RegistryReader)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/obsoletes.cs b/src/NServiceBus.Core/obsoletes.cs new file mode 100644 index 00000000000..0919bddc39f --- /dev/null +++ b/src/NServiceBus.Core/obsoletes.cs @@ -0,0 +1,2610 @@ +// ReSharper disable UnusedTypeParameter +// ReSharper disable UnusedParameter.Local + + +using NServiceBus; + +#pragma warning disable 1591 +// ReSharper disable UnusedParameter.Global + +/* Types not obsoleted since they would clash with the new `NServiceBus.Saga` type +- `NServiceBus.Saga.ContainSagaData` +- `NServiceBus.Saga.IAmStartedByMessages` +- `NServiceBus.Saga.IConfigureHowToFindSagaWithMessage` +- `NServiceBus.Saga.IContainSagaData` +- `NServiceBus.Saga.IFinder` +- `NServiceBus.Saga.IFindSagas` +- `NServiceBus.Saga.IHandleSagaNotFound` +- `NServiceBus.Saga.IHandleTimeouts` +- `NServiceBus.Saga.ISagaPersister` +- `NServiceBus.Saga.Saga` +- `NServiceBus.Saga.Saga` +- `NServiceBus.Saga.SagaPropertyMapper` +- `NServiceBus.Saga.ToSagaExpression` +- `NServiceBus.Saga.UniqueAttribute` +*/ + +/* Types moved to the host package +- `NServiceBus.EndpointNameAttribute` +- `NServiceBus.EndpointSLAAttribute` +- `NServiceBus.IConfigureThisEndpoint` +*/ + +namespace NServiceBus +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Text; + using DataBus; + using ObjectBuilder; + + public static partial class ConfigureCriticalErrorAction + { + [ObsoleteEx( + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6", + ReplacementTypeOrMember = "ConfigureCriticalErrorAction.DefineCriticalErrorAction(EndpointConfiguration, Func)")] + public static void DefineCriticalErrorAction(this EndpointConfiguration endpointConfiguration, Action onCriticalError) + { + } + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext` provided to message handlers instead.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IMessageContext + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Replaced by NServiceBus.Callbacks package")] + public interface ICallback + { + } + + [ObsoleteEx( + Message = "Use the string based overloads", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class Address + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Replaced by NServiceBus.Callbacks package")] + public class CompletionResult + { + } + + [ObsoleteEx( + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public static class ConfigureInMemoryFaultManagement + { + [ObsoleteEx( + Message = "This is no longer supported. If you want full control over what happens when a message fails (including retries) override the MoveFaultsToErrorQueue behavior.", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public static void DiscardFailedMessagesInsteadOfSendingToErrorQueue(this EndpointConfiguration config) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "EndpointConfiguration.ExecuteTheseHandlersFirst")] + public interface ISpecifyMessageHandlerOrdering + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "EndpointConfiguration.ExecuteTheseHandlersFirst")] + public class First + { + } + + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "endpointConfiguration.Recoverability().Delayed(delayed => )")] + public static class SecondLevelRetriesConfigExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "endpointConfiguration.Recoverability().Delayed(delayed => )")] + public static SecondLevelRetriesSettings SecondLevelRetries(this EndpointConfiguration config) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "endpointConfiguration.Recoverability().CustomPolicy(Func @custom)")] + public class SecondLevelRetriesSettings + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "endpointConfiguration.Recoverability().CustomPolicy(Func @custom)")] + public void CustomRetryPolicy(Func customPolicy) + { + throw new NotImplementedException(); + } + } + + + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "EndpointConfiguration.ExecuteTheseHandlersFirst")] + public class Order + { + } + + [ObsoleteEx( + ReplacementTypeOrMember = "EndpointConfiguration", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class BusConfiguration + { + } + + public partial class EndpointConfiguration + { + [ObsoleteEx( + ReplacementTypeOrMember = "EndpointConfiguration.AddHeaderToAllOutgoingMessages(string key,string value)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public IDictionary OutgoingHeaders + { + get { throw new NotImplementedException(); } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.ExcludeTypes")] + public void TypesToScan(IEnumerable typesToScan) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.ExcludeAssemblies")] + public void AssembliesToScan(IEnumerable assemblies) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.ExcludeAssemblies")] + public void AssembliesToScan(params Assembly[] assemblies) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.ExcludeAssemblies")] + public void ScanAssembliesInDirectory(string probeDirectory) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + ReplacementTypeOrMember = "EndpointConfiguration.OverridePublicReturnAddress(string address)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public void OverridePublicReturnAddress(Address address) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Endpoint name is now a mandatory constructor argument on EndpointConfiguration.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public void EndpointName(string name) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + Message = "This is no longer a public API", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class Configure + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.ExcludeAssemblies")] + public class AllAssemblies + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public interface IExcludesBuilder + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public interface IIncludesBuilder + { + } + + [ObsoleteEx( + Message = "Not used anymore, use `OutgoingMessage` or `IncomingMessage` instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class TransportMessage + { + [ObsoleteEx( + Message = "For sending purposes use `DeliveryConstraintContextExtensions.AddDeliveryConstraint(new NonDurableDelivery())` to set NonDurable delivery or `NonDurableDelivery constraint;DeliveryConstraintContextExtensions.TryGetDeliveryConstraint(out constraint)` to read wether NonDurable delivery is set. When receiving look at the new 'NServiceBus.NonDurableMessage' header", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public bool Recoverable + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + Message = "For sending purposes use `DeliveryConstraintContextExtensions.AddDeliveryConstraint(new DiscardIfNotReceivedBefore(timeToBeReceived))` to set the `TimeToBeReceived` or `DiscardIfNotReceivedBefore constraint;DeliveryConstraintContextExtensions.TryGetDeliveryConstraint(out constraint)` to read the `TimeToBeReceived`. When receiving look at the new 'NServiceBus.TimeToBeReceived' header", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public TimeSpan TimeToBeReceived + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + + [ObsoleteEx( + Message = "Use the value of the 'NServiceBus.CorrelationId' header instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public string CorrelationId + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + Message = "Use the value of the 'IncomingMessage.Body' or 'OutgoingMessage.Body' instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public byte[] Body + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + Message = "Use the value of the 'IncomingMessage.MessageId' or 'OutgoingMesssage.MessageId' instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public string Id + { + get { throw new NotImplementedException(); } + } + + [ObsoleteEx( + ReplacementTypeOrMember = "GetReplyToAddress(this IncomingMessage message)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public string ReplyToAddress + { + get { throw new NotImplementedException(); } + } + + [ObsoleteEx( + ReplacementTypeOrMember = "GetMessageIntent(this IncomingMessage message)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public MessageIntentEnum MessageIntent + { + get { throw new NotImplementedException(); } + } + + [ObsoleteEx( + Message = "Use the value of the 'IncomingMessage.Headers' or 'OutgoingMesssage.Headers' instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public Dictionary Headers { get; } = new Dictionary(); + } + + [ObsoleteEx( + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6", + ReplacementTypeOrMember = "Notifications")] + public class BusNotifications + { + } + + public partial class Notifications + { + [ObsoleteEx(Message = "For performance reasons it is no longer possible to instrument the pipeline execution", RemoveInVersion = "7.0", TreatAsErrorFromVersion = "6.0")] + public PipelineNotifications Pipeline + { + get { throw new NotImplementedException(); } + } + } + + [ObsoleteEx(Message = "For performance reasons it is no longer possible to instrument the pipeline execution", RemoveInVersion = "7.0", TreatAsErrorFromVersion = "6.0")] + public class PipelineNotifications + { + } + + public static partial class ConfigureRijndaelEncryptionService + { + [ObsoleteEx( + ReplacementTypeOrMember = "RegisterEncryptionService(this EndpointConfiguration config, Func func)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "It is no longer possible to access the builder to create an encryption service. If container access is required use the container directly in the factory.")] + public static void RegisterEncryptionService(this EndpointConfiguration config, Func func) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + Message = "`IWantToRunWhenBusStartsAndStops` has been moved to the host implementations and renamed. If you're self-hosting, instead of using this interface, you can call any startup code right before `Endpoint.Create` or any cleanup code right after `Endpoint.Stop`. When using either NServiceBus.Host or NServiceBus.Host.AzureCloudService, use the host's interface `IWantToRunWhenEndpointStartsAndStops` instead.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IWantToRunWhenBusStartsAndStops + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "PersistenceExtensions")] + public class PersistenceExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "PersistenceExtensions")] + public class PersistenceExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "PersistenceExtensions")] + public class PersistenceExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "SerializationExtensions")] + public class SerializationExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public static class ScaleOutExtentions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public static Settings.ScaleOutSettings ScaleOut(this EndpointConfiguration config) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "SettingsExtensions")] + public static class SettingsExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "LoadMessageHandlersExtensions")] + public static class LoadMessageHandlersExtentions + { + } + + public static partial class ConfigureFileShareDataBus + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "BasePath(this DataBusExtensions config, string basePath)")] + public static DataBusExtentions BasePath(this DataBusExtentions config, string basePath) + { + throw new NotImplementedException(); + } + } + + public static partial class JsonSerializerConfigurationExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Encoding(this SerializationExtensions config, Encoding encoding)")] + public static void Encoding(this SerializationExtentions config, Encoding encoding) + { + throw new NotImplementedException(); + } + } + + public static partial class XmlSerializationExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "DontWrapRawXml(this SerializationExtensions config)")] + public static SerializationExtentions DontWrapRawXml(this SerializationExtentions config) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Namespace(this SerializationExtensions config, string namespaceToUse)")] + public static SerializationExtentions Namespace(this SerializationExtentions config, string namespaceToUse) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "SanitizeInput(this SerializationExtensions config)")] + public static SerializationExtentions SanitizeInput(this SerializationExtentions config) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Unicast +{ + using System; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Not a public API", + ReplacementTypeOrMember = "MessageHandlerRegistry")] + public interface IMessageHandlerRegistry + { + } + + [ObsoleteEx( + ReplacementTypeOrMember = "NServiceBus.UnicastBus.DeliveryMessageOptions", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public abstract class DeliveryOptions + { + [ObsoleteEx( + Message = "Reply to address can be get/set using the `NServiceBus.ReplyToAddress` header", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public string ReplyToAddress + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + Message = "Turn best practices check off using configuration.DisableFeature()", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public bool EnforceMessagingBestPractices + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } + + [ObsoleteEx( + Message = "Use context.Intent to detect of the message is a event being published and use context.MessageType to get the actual event type", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class PublishOptions : DeliveryOptions + { + } + + [ObsoleteEx( + ReplacementTypeOrMember = "NServiceBus.UnicastBus.SendMessageOptions", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class SendOptions : DeliveryOptions + { + [ObsoleteEx( + ReplacementTypeOrMember = "SendMessageOptions(string)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + // ReSharper disable once UnusedParameter.Local + public SendOptions(Address destination) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Reply to address can be get/set using the `NServiceBus.CorrelationId` header", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public string CorrelationId + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + ReplacementTypeOrMember = "DelayDeliveryFor", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public TimeSpan? DelayDeliveryWith + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class MessageContext + { + } +} + +namespace NServiceBus.Timeout.Core +{ + using System; + using Unicast; + + public partial class TimeoutData + { + [ObsoleteEx( + Message = "Use new OutgoingMessage(timeoutData.State) instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public TransportMessage ToTransportMessage() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use new SendOptions() instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public SendOptions ToSendOptions(Address replyToAddress) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use new SendOptions() instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public SendOptions ToSendOptions(string replyToAddress) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Not used anymore", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public const string OriginalReplyToAddress = "NServiceBus.Timeout.ReplyToAddress"; + } +} + +namespace NServiceBus.Unicast +{ + [ObsoleteEx( + Message = "Not used anymore, use the 'NServiceBus.MessageIntent' header to detect if the message is a reply", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class ReplyOptions + { + } +} + +namespace NServiceBus.MessageMutator +{ + [ObsoleteEx( + Message = "Have the mutator implement both IMutateOutgoingMessages and IMutateIncomingMessages ", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IMessageMutator + { + } + + [ObsoleteEx( + Message = "Have the mutator implement both IMutateIncomingTransportMessages and IMutateOutgoingTransportMessages", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IMutateTransportMessages : IMutateIncomingTransportMessages, IMutateOutgoingTransportMessages + { + } +} + +namespace NServiceBus.Unicast +{ + using System; + using System.Collections.Generic; + + [ObsoleteEx( + Message = "UnicastBus has been made internal. Use IEndpointInstance instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public class UnicastBus + { + } + + public partial class MessageHandlerRegistry + { + [ObsoleteEx( + ReplacementTypeOrMember = "MessageHandlerRegistry.GetHandlersFor(Type messageType)", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public IEnumerable GetHandlerTypes(Type messageType) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + ReplacementTypeOrMember = "MessageHandler.Invoke(object message, object context)", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public void InvokeHandle(object handler, object message) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + ReplacementTypeOrMember = "MessageHandler.Invoke(object message, object context)", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public void InvokeTimeout(object handler, object state) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + ReplacementTypeOrMember = "MessageHandlerRegistry.RegisterHandler(Type handlerType)", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public void CacheMethodForHandler(Type handler, Type messageType) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Unicast.Behaviors +{ + using System; + + public class MessageHandler + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "NServiceBus.Pipeline.MessageHandler(Action invocation, Type handlerType)")] + public MessageHandler() + { + throw new NotImplementedException("Creator of the message handler must assign the handler type and the invocation delegate"); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "NServiceBus.Pipeline.MessageHandler.Invoke")] + public Action Invocation + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } +} + +namespace NServiceBus.Unicast.Messages +{ + using System; + + public partial class MessageMetadata + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "You can access TTBR via the DeliveryConstraints collection on the outgoing context")] + public TimeSpan TimeToBeReceived + { + get { throw new NotImplementedException(); } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "You can access Recoverable via the DeliveryConstraints collection on the outgoing context, the new constraint is called NonDurableDelivery")] + public bool Recoverable + { + get { throw new NotImplementedException(); } + } + } +} + +namespace NServiceBus +{ + using System; + + public partial class Conventions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer an extension point")] + public TimeSpan GetTimeToBeReceived(Type messageType) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer an extension point")] + public static bool IsExpressMessageType(Type t) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus +{ + using System; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Headers are not managed via the send, reply and publishoptions")] + public static class ExtensionMethods + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use a incoming behavior to get access to the current message")] + public static object CurrentMessageBeingHandled + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Headers are not 'set' only on the outgoing pipeline")] + public static string GetMessageHeader(this IBus bus, object msg, string key) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = @"Use the overload of the Send, Publish or Reply method that accepts an options parameter. Call options.SetHeader(""MyHeader"",""MyValue"") instead.")] + public static void SetMessageHeader(this IBus bus, object msg, string key, string value) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Transports.Msmq +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "The msmq transaction is now available via the pipeline context")] + public class MsmqUnitOfWork + { + } +} + +namespace NServiceBus.Unicast +{ + using System; + + public class DeliveryMessageOptions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the ConsistencyGuarantee class instead")] + public bool EnlistInReceiveTransaction + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use context.TryGetDeliveryConstraint instead")] + public TimeSpan? TimeToBeReceived + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use context.TryGetDeliveryConstraint instead")] + public bool? NonDurable + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } +} + +namespace NServiceBus.Features +{ + using System; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, safe to remove")] + public class StorageDrivenPublishing + { + } + + [ObsoleteEx( + Message = "Use the ConfigureSerialization Feature class instead", + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = "ConfigureSerialization")] + public static class SerializationFeatureHelper + { + } + + public partial class Feature + { + [ObsoleteEx(ReplacementTypeOrMember = "FeatureConfigurationContext.RegisterStartupTask", RemoveInVersion = "7.0", TreatAsErrorFromVersion = "6.0")] + protected void RegisterStartupTask() where T : FeatureStartupTask + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + Message = "Use extensions provided by the TransportDefinition class instead", + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0")] + public class ConfigureTransport + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Encryption is no longer enabled by default. Encryption gets enabled by calling configuration.RegisterEncryptionService or configuration.RijndaelEncryptionService.")] + public class Encryptor { } +} + +namespace NServiceBus.Transports +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.Transport.IDispatchMessages")] + public interface IDeferMessages + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.Transport.ICancelDeferredMessages")] + void ClearDeferredMessages(string headerKey, string headerValue); + } +} + +namespace NServiceBus.Transports +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.Transport.IDispatchMessages")] + public interface IPublishMessages + { + } +} + +namespace NServiceBus.Transports +{ + using Unicast; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.Transport.IDispatchMessages")] + public interface ISendMessages + { + void Send(TransportMessage message, SendOptions sendOptions); + } +} + +namespace NServiceBus.Features +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, safe to remove")] + public class TimeoutManagerBasedDeferral + { + } +} + +namespace NServiceBus.Transports +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, safe to remove")] + public interface IAuditMessages + { + } +} + +namespace NServiceBus.Unicast.Subscriptions +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, safe to remove")] + public class SubscriptionEventArgs + { + } +} + +namespace NServiceBus.Unicast.Routing +{ + using System; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, safe to remove")] + public class StaticMessageRouter + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "config.AutoSubscribe().AutoSubscribePlainMessages()")] + public bool SubscribeToPlainMessages + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + } +} + +namespace NServiceBus.AutomaticSubscriptions.Config +{ + using System; + + public partial class AutoSubscribeSettings + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Transports with support for centralized pubsub will default this to true. Can safely be removed")] + public void DoNotRequireExplicitRouting() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Encourages bad practices. IMessageSession.Subscribe should be explicitly used.")] + public void AutoSubscribePlainMessages() + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Config +{ + using System; + using System.Configuration; + + [ObsoleteEx( + Message = "Use the feature concept instead via A class which inherits from `NServiceBus.Features.Feature` and use `configuration.EnableFeature()`", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public interface IWantToRunWhenConfigurationIsComplete + { + } + + [ObsoleteEx( + Message = Error, + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public class SecondLevelRetriesConfig : ConfigurationSection + { + const string Error = "Second Level Retries has been renamed to Delayed Retries. The app.config API has been removed, use the code API via endpointConfiguration.Recoverability().Delayed(settings => ...);."; + + public SecondLevelRetriesConfig() + { + Properties.Add(new ConfigurationProperty("Enabled", typeof(bool), true)); + Properties.Add(new ConfigurationProperty("TimeIncrease", typeof(TimeSpan), Recoverability.DefaultTimeIncrease, null, new TimeSpanValidator(TimeSpan.Zero, TimeSpan.MaxValue), ConfigurationPropertyOptions.None)); + Properties.Add(new ConfigurationProperty("NumberOfRetries", typeof(int), Recoverability.DefaultNumberOfRetries, null, new IntegerValidator(0, int.MaxValue), ConfigurationPropertyOptions.None)); + } + + [ObsoleteEx( + Message = Error + " To disable use endpointConfiguration.Recoverability().Delayed(settings => settings.NumberOfRetries(0));", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public bool Enabled + { + get { return (bool) this["Enabled"]; } + set { this["Enabled"] = value; } + } + + [ObsoleteEx( + Message = Error + " To change the TimeIncrease use endpointConfiguration.Recoverability().Delayed(settings => settings.TimeIncrease(TimeSpan.FromMinutes(5));", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public TimeSpan TimeIncrease + { + get { return (TimeSpan) this["TimeIncrease"]; } + set { this["TimeIncrease"] = value; } + } + + [ObsoleteEx( + Message = Error + " To change the NumberOfRetries use endpointConfiguration.Recoverability().Delayed(settings => settings.NumberOfRetries(5);", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public int NumberOfRetries + { + get { return (int) this["NumberOfRetries"]; } + set { this["NumberOfRetries"] = value; } + } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.EnlistWithLegacyMSMQDistributor")] + public class MasterNodeConfig : ConfigurationSection + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.EnlistWithLegacyMSMQDistributor")] + [ConfigurationProperty("Node", IsRequired = false)] + public string Node { get; set; } + } + + [ObsoleteEx( + Message = Error, + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public class TransportConfig : ConfigurationSection + { + const string Error = "The app.config API TransportConfig has been removed, use the code API."; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = Error + " To change the concurrency level use endpointConfiguration.LimitMessageProcessingConcurrencyTo(1);")] + [ConfigurationPropertyAttribute("MaximumConcurrencyLevel", DefaultValue = 0, IsRequired = false)] + public int MaximumConcurrencyLevel + { + get { return (int) this["MaximumConcurrencyLevel"]; } + set { this["MaximumConcurrencyLevel"] = value; } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = Error + " To change the NumberOfRetries use endpointConfiguration.Recoverability().Immediate(settings => settings.NumberOfRetries(5);")] + [ConfigurationPropertyAttribute("MaxRetries", DefaultValue = 5, IsRequired = false)] + public int MaxRetries + { + get { return (int) this["MaxRetries"]; } + set { this["MaxRetries"] = value; } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Message throughput throttling has been removed. Consult the documentation for further information.")] + [ConfigurationPropertyAttribute("MaximumMessageThroughputPerSecond", DefaultValue = -1, IsRequired = false)] + public int MaximumMessageThroughputPerSecond + { + get { return (int) this["MaximumMessageThroughputPerSecond"]; } + set { this["MaximumMessageThroughputPerSecond"] = value; } + } + } + + public partial class UnicastBusConfig + { + [ConfigurationProperty("DistributorControlAddress", IsRequired = false)] + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Switch to the code API by using 'EndpointConfiguration.EnlistWithLegacyMSMQDistributor' instead.")] + public string DistributorControlAddress + { + get + { + var result = this["DistributorControlAddress"] as string; + if (string.IsNullOrWhiteSpace(result)) + { + result = null; + } + + return result; + } + set { this["DistributorControlAddress"] = value; } + } + + [ConfigurationProperty("DistributorDataAddress", IsRequired = false)] + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Switch to the code API by using 'EndpointConfiguration.EnlistWithLegacyMSMQDistributor' instead.")] + public string DistributorDataAddress + { + get + { + var result = this["DistributorDataAddress"] as string; + if (string.IsNullOrWhiteSpace(result)) + { + result = null; + } + + return result; + } + set { this["DistributorDataAddress"] = value; } + } + + [ConfigurationProperty("ForwardReceivedMessagesTo", IsRequired = false)] + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use 'EndpointConfiguration.ForwardReceivedMessagesTo' to configure the forwarding address.")] + public string ForwardReceivedMessagesTo + { + get + { + var result = this["ForwardReceivedMessagesTo"] as string; + if (string.IsNullOrWhiteSpace(result)) + { + result = null; + } + + return result; + } + set { this["ForwardReceivedMessagesTo"] = value; } + } + } +} + +namespace NServiceBus.SecondLevelRetries.Config +{ + using System; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.SecondLevelRetriesSettings")] + public class SecondLevelRetriesSettings + { + /// + /// Register a custom retry policy. + /// + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.SecondLevelRetriesSettings.CustomRetryPolicy(Func customPolicy)")] + public void CustomRetryPolicy(Func customPolicy) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Faults +{ + using System; + + public partial class ErrorsNotifications + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = nameof(MessageHasBeenSentToDelayedRetries) + )] + public EventHandler MessageHasBeenSentToSecondLevelRetries; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0", + ReplacementTypeOrMember = nameof(MessageHasFailedAnImmediateRetryAttempt))] + public EventHandler MessageHasFailedAFirstLevelRetryAttempt; + } + + [ObsoleteEx( + Message = "First Level Retries has been renamed to Immediate Retries", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6", + ReplacementTypeOrMember = "NServiceBus.Faults.ImmediateRetryMessage")] + public struct FirstLevelRetry + { + } + [ObsoleteEx( + Message = "Second Level Retries has been renamed to Delayed Retries", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6", + ReplacementTypeOrMember = "NServiceBus.Faults.DelayedRetryMessage")] + public struct SecondLevelRetry + { + } + + [ObsoleteEx( + Message = "IManageMessageFailures is no longer an extension point. To take control of the error handling part of the message processing pipeline, review the Version 5 to 6 upgrade guide for details.", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public interface IManageMessageFailures + { + } +} + +namespace NServiceBus.Sagas +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "There is no need for this attribute anymore, all mapped properties are automatically correlated.")] + public sealed class UniqueAttribute : Attribute + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the new SagaMetadata")] + public static PropertyInfo GetUniqueProperty(Type type) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the new SagaMetadata")] + public static KeyValuePair? GetUniqueProperty(IContainSagaData entity) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the new SagaMetadata")] + public static IDictionary GetUniqueProperties(IContainSagaData entity) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the new SagaMetadata")] + public static IEnumerable GetUniqueProperties(Type type) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Persistence +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "NServiceBus.Persistence.StorageType")] + public enum Storage + { + Timeouts = 1, + Subscriptions = 2, + Sagas = 3, + GatewayDeduplication = 4, + Outbox = 5 + } +} + +namespace NServiceBus.Unicast.Queuing +{ + [ObsoleteEx( + ReplacementTypeOrMember = "QueueBindings", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IWantQueueCreated + { + } +} + +namespace NServiceBus.Transports +{ + using System; + using Unicast.Transport; + + [ObsoleteEx( + ReplacementTypeOrMember = "NServiceBus.Transport.IPushMessages", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public interface IDequeueMessages + { + void Init(Address address, TransactionSettings transactionSettings, Func tryProcessMessage, Action endProcessMessage); + void Start(int maximumConcurrencyLevel); + void Stop(); + } +} + +namespace NServiceBus.Transports.Msmq +{ + [ObsoleteEx( + Message = "No longer available, resolve an instance of IPushMessages from the container instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class MsmqDequeueStrategy + { + } +} + +namespace NServiceBus.Unicast.Transport +{ + using System; + using System.Transactions; + + [ObsoleteEx( + Message = "Transaction settings is no longer available via this class. See obsoletes on individual members for further details", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public class TransactionSettings + { + [ObsoleteEx( + Message = "No longer used", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public TransactionSettings(bool isTransactional, TimeSpan transactionTimeout, IsolationLevel isolationLevel, bool suppressDistributedTransactions, bool doNotWrapHandlersExecutionInATransactionScope) + { + } + + [ObsoleteEx( + Message = "Timeouts are now controlled explicitly for the transaction scope unit of work using config.UnitOfWork().WrapHandlersInATransactionScope(timeout: X)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public TimeSpan TransactionTimeout { get; set; } + + [ObsoleteEx( + Message = "Isolation level are now controlled explicitly for the transaction scope unit of work using config.UnitOfWork().WrapHandlersInATransactionScope(isolationlevel: X)", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public IsolationLevel IsolationLevel { get; set; } + + [ObsoleteEx( + Message = "DoNotWrapHandlersExecutionInATransactionScope is no longer used here. Use settings.GetOrDefault('Transactions.DoNotWrapHandlersExecutionInATransactionScope') instead", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public bool DoNotWrapHandlersExecutionInATransactionScope { get; set; } + + [ObsoleteEx( + Message = "SuppressDistributedTransactions is no longer used here. Use `context.Settings.GetRequiredTransactionModeForReceives() != Transactions.TransactionScope` instead.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public bool SuppressDistributedTransactions { get; set; } + + [ObsoleteEx( + Message = "IsTransactional is no longer used here. Use `context.Settings.GetRequiredTransactionModeForReceives() != Transactions.None` instead.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public bool IsTransactional { get; set; } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use the pipeline to catch failures")] + public class FailedMessageProcessingEventArgs : EventArgs + { + } +} + +namespace NServiceBus.Settings +{ + using System; + + [ObsoleteEx(TreatAsErrorFromVersion = "6", RemoveInVersion = "7")] + public class ScaleOutSettings + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "This is the default starting with V6.")] + public void UseSingleBrokerQueue() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Not required any more as for MSMQ this is the default behavior and for other transports the unique instance ID has to be provided.")] + public void UseUniqueBrokerQueuePerMachine() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Not required any more as for MSMQ this is the default behavior and for other transports the unique instance ID has to be provided.")] + public void UniqueQueuePerEndpointInstance() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.MakeInstanceUniquelyAddressable(string discriminator)")] + public void UniqueQueuePerEndpointInstance(string discriminator) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Routing.StorageDrivenPublishing +{ + using System; + using System.Collections.Generic; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer an extension point, if you want to list events without subscribers you can take a dependency on ISubscriptionStorage and query it for the event types you want to check")] + public class SubscribersForEvent + { + public SubscribersForEvent(List subscribers, Type eventType) + { + throw new NotImplementedException(); + } + + public IEnumerable Subscribers { get; private set; } + + public Type EventType { get; private set; } + } +} + +namespace NServiceBus +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, use the new callbacks api described in the version 6 upgrade guide")] + public class BusAsyncResultEventArgs + { + } +} + +namespace NServiceBus.Unicast +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, use the new callbacks api described in the version 6 upgrade guide")] + public class BusAsyncResult + { + } +} + +namespace NServiceBus +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public interface IManageMessageHeaders + { + } +} + +namespace NServiceBus.Pipeline.Contexts +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "OutgoingLogicalMessage")] + public class OutgoingContext + { + } +} + +namespace NServiceBus.Pipeline +{ + using System; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Behavior")] + public interface IBehavior + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "The pipeline context is no longer avaliable via dependency injection. Use a custom behavior as described in the version 6 upgrade guide")] + public class PipelineExecutor + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "WellKnownSteps are obsolete. Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public class WellKnownStep + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static WellKnownStep HostInformation; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static WellKnownStep ProcessingStatistics; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep AuditProcessedMessage; + + [ObsoleteEx( + Message = "The child container creation is now an integral part of the pipeline invocation and no longer a separate behavior.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public static readonly WellKnownStep CreateChildContainer; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep ExecuteUnitOfWork; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep MutateIncomingTransportMessage; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep DispatchMessageToTransport; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep InvokeHandlers; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep MutateIncomingMessages; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep InvokeSaga; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep MutateOutgoingMessages; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep MutateOutgoingTransportMessage; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep EnforceSendBestPractices; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep EnforceReplyBestPractices; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep EnforcePublishBestPractices; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep EnforceSubscribeBestPractices; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public static readonly WellKnownStep EnforceUnsubscribeBestPractices; + } + + public abstract partial class RegisterStep + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public void InsertBeforeIfExists(WellKnownStep step) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public void InsertBefore(WellKnownStep step) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public void InsertAfterIfExists(WellKnownStep step) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Use an appropriate pipeline stage for your behavior instead. Consult the pipeline extension documentation for more information.")] + public void InsertAfter(WellKnownStep step) + { + throw new NotImplementedException(); + } + } + + public partial class PipelineSettings + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Remove(string stepId)")] + public void Remove(WellKnownStep wellKnownStep) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Replace(string stepId, Type newBehavior, string description)")] + public void Replace(WellKnownStep wellKnownStep, Type newBehavior, string description = null) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Satellites +{ + [ObsoleteEx( + Message = "No longer an extension point. Instead create a Feature and use FeatureConfigurationContext.AddSatelliteReceiver(...).", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public interface IAdvancedSatellite + { + } + + [ObsoleteEx( + Message = "No longer an extension point. Instead create a Feature and use FeatureConfigurationContext.AddSatelliteReceiver(...).", + RemoveInVersion = "7", + TreatAsErrorFromVersion = "6")] + public interface ISatellite + { + } +} + +namespace NServiceBus.Unicast.Transport +{ + using System; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public static class ControlMessage + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "NServiceBus.Transport.IPushMessages")] + public interface ITransport + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public class TransportMessageReceivedEventArgs + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public class StartedMessageProcessingEventArgs + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public class FinishedMessageProcessingEventArgs : EventArgs + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public class TransportMessageAvailableEventArgs + { + } +} + +namespace NServiceBus.Transports +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "The namespace NServiceBus.Transports was renamed to NServiceBus.Transport.", + ReplacementTypeOrMember = "NServiceBus.Transport.TransportDefinition")] + public abstract class TransportDefinition + { + } +} + +namespace NServiceBus.Transport +{ + using System; + + public abstract partial class TransportDefinition + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use TransportInfrastructure.TransactionMode == TransportTransactionMode.TransactionScope instead.")] + public bool? HasSupportForDistributedTransactions + { + get { throw new NotImplementedException(); } + protected set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use TransportInfrastructure.TransactionMode == TransportTransactionMode.SendsAtomicWithReceive instead.")] + public bool HasSupportForMultiQueueNativeTransactions + { + get { throw new NotImplementedException(); } + protected set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use TransportInfrastructure.OutboundRoutingPolicy.Publishes == OutboundRoutingType.Multicast instead.")] + public bool HasNativePubSubSupport + { + get { throw new NotImplementedException(); } + protected set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "The concept of centralized publish and subscribe is no longer available.")] + public bool HasSupportForCentralizedPubSub + { + get { throw new NotImplementedException(); } + protected set { throw new NotImplementedException(); } + } + } +} + +#pragma warning disable 0067 + +namespace NServiceBus.Unicast.Transport +{ + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "No longer used, can safely be removed")] + public class TransportReceiver + { + } +} + +#pragma warning restore 0067 + +namespace NServiceBus +{ + public static partial class Headers + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "The WinIdName header is no longer attached to outgoing message to avoid passing security related information on the wire. Should you rely on the header being present you can add a message mutator that sets it.")] + public const string WindowsIdentityName = "WinIdName"; + } +} + +namespace NServiceBus +{ + using System; + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use IEndpointInstance to create sending session.")] + public interface ISendOnlyBus : IDisposable + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "IHandleMessages now exposes the IMessageHandlerContext parameter. Use this to access what used to be available in the IBus interface. Use the provided context in extension points like message handlers or IEndpointInstance when outside the message processing pipeline.")] + public interface IBus + { + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "IStartableEndpoint")] + public interface IStartableBus : IBus + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "IStartableEndpoint")] + IBus Start(); + } + + public static class Bus + { + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Endpoint.Create")] + public static IStartableBus Create(EndpointConfiguration configuration) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "EndpointConfiguration.SendOnly")] + public static IBus CreateSendOnly(EndpointConfiguration configuration) + { + throw new NotImplementedException(); + } + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + Message = "Use extension methods provided on ISendOnlyBus")] + public class Schedule + { + } +} + +namespace NServiceBus.Hosting.Helpers +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + public partial class AssemblyScanner + { + [ObsoleteEx( + Message = "This method is no longer required since deep scanning of assemblies is done to detect an NServiceBus reference.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public List MustReferenceAtLeastOneAssembly + { + get { throw new NotImplementedException(); } + } + } +} + +namespace NServiceBus.Outbox +{ + using System; + + public partial class OutboxSettings + { + [ObsoleteEx( + ReplacementTypeOrMember = "InMemoryOutboxSettingsExtensions.TimeToKeepDeduplicationData(TimeSpan time)", + TreatAsErrorFromVersion = "6.0", + RemoveInVersion = "7.0")] + public void TimeToKeepDeduplicationData(TimeSpan time) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus +{ + using System; + + public partial class Saga + { + [ObsoleteEx( + Message = "Sagas no longer provide access to bus operations via the .Bus property. Use the context parameter on the Handle method.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public IBus Bus + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "RequestTimeout(IMessageHandlerContext, DateTime)")] + protected void RequestTimeout(DateTime at) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Construct the message and pass it to the non Action overload.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "RequestTimeout(IMessageHandlerContext DateTime, TTimeoutMessageType)")] + protected void RequestTimeout(DateTime at, Action action) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "RequestTimeout(IMessageHandlerContext, DateTime, TTimeoutMessageType)")] + protected void RequestTimeout(DateTime at, TTimeoutMessageType timeoutMessage) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "RequestTimeout(IMessageHandlerContext, TimeSpan)")] + protected void RequestTimeout(TimeSpan within) + { + throw new NotImplementedException(); + } + + + [ObsoleteEx( + Message = "Construct the message and pass it to the non Action overload.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Saga.RequestTimeout(IMessageHandlerContext, TimeSpan, TTimeoutMessageType)")] + protected void RequestTimeout(TimeSpan within, Action messageConstructor) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "RequestTimeout(IMessageHandlerContext, TimeSpan, TTimeoutMessageType)")] + protected void RequestTimeout(TimeSpan within, TTimeoutMessageType timeoutMessage) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "ReplyToOriginator(IMessageHandlerContext, object)")] + protected void ReplyToOriginator(object message) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Construct the message and pass it to the non Action overload.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "ReplyToOriginator(IMessageHandlerContext, object)")] + protected virtual void ReplyToOriginator(Action messageConstructor) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus +{ + using System; + + public static class IBusExtensions + { + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.Reply(object message)` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void Reply(this IBus bus, object message) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.Reply(Action messageConstructor)` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void Reply(this IBus bus, Action messageConstructor) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.SendLocal(object message)` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void SendLocal(this IBus bus, object message) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.SendLocal(Action messageConstructor)` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void SendLocal(this IBus bus, Action messageConstructor) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.HandleCurrentMessageLater()` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void HandleCurrentMessageLater(this IBus bus) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.ForwardCurrentMessageTo(string destination)` provided to message handlers instead.", + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7")] + public static void ForwardCurrentMessageTo(this IBus bus, string destination) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + Message = "Use `IMessageHandlerContext.DoNotContinueDispatchingCurrentMessageToHandlers()` provided to message handlers instead.", + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0")] + public static void DoNotContinueDispatchingCurrentMessageToHandlers(this IBus bus) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Subscribe(Type messageType)")] + public static void Subscribe(this IBus bus, Type messageType) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Subscribe()")] + public static void Subscribe(this IBus bus) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Unsubscribe(Type messageType)")] + public static void Unsubscribe(this IBus bus, Type messageType) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + TreatAsErrorFromVersion = "6", + RemoveInVersion = "7", + ReplacementTypeOrMember = "Unsubscribe()")] + public static void Unsubscribe(this IBus bus) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UseTransport().SubscriptionAuthorizer(Authorizer);")] + public interface IAuthorizeSubscriptions + { + } +} + +namespace NServiceBus.Settings +{ + using System; + using System.Transactions; + + public class TransactionSettings + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = + @"DoNotWrapHandlersExecutionInATransactionScope() has been removed since transaction scopes are no longer used by non DTC transports delay the dispatch of all outgoing operations until handlers have been executed. +In Version 6 handlers will only be wrapped in a transactionscope if running the MSMQ or SQLServer transports in default mode. This means that performing storage operations against data sources also supporting transaction scopes +will escalate to a distributed transaction. Previous versions allowed opting out of this behavior using config.Transactions().DoNotWrapHandlersExecutionInATransactionScope(). In Version 6 it's recommended to use `EndpointConfiguration.UseTransport().Transactions(TransportTransactionMode.ReceiveOnly)` to lean on native transport transaction and the new batched dispatch support to achieve the same level of consistency with better performance. +Suppressing the ambient transaction created by the MSMQ and SQL Server transports can still be achieved by creating a custom pipeline behavior with a suppressed transaction scope.")] + public TransactionSettings DoNotWrapHandlersExecutionInATransactionScope() + { + throw new NotImplementedException(); + } + + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UseTransport().Transactions(TransportTransactionMode.None);")] + public TransactionSettings Disable() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UseTransport().Transactions(TransportTransactionMode.ReceiveOnly|TransportTransactionMode.SendsAtomicWithReceive);")] + public TransactionSettings Enable() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UseTransport().Transactions(TransportTransactionMode.ReceiveOnly|TransportTransactionMode.SendsAtomicWithReceive);")] + public TransactionSettings DisableDistributedTransactions() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UseTransport().Transactions(TransportTransactionMode.TransactionScope);")] + public TransactionSettings EnableDistributedTransactions() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UnitOfWork().WrapHandlersInATransactionScope(isolationLevel: IsolationLevel.X);")] + public TransactionSettings IsolationLevel(IsolationLevel isolationLevel) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UnitOfWork().WrapHandlersInATransactionScope();")] + public TransactionSettings WrapHandlersExecutionInATransactionScope() + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "config.UnitOfWork().WrapHandlersInATransactionScope(timeout: TimeSpan.FromSeconds(X));")] + public TransactionSettings DefaultTimeout(TimeSpan defaultTimeout) + { + throw new NotImplementedException(); + } + } + + namespace NServiceBus + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, can safely be removed")] + public static class TransactionSettingsExtentions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer used, can safely be removed")] + public static TransactionSettings Transactions(this EndpointConfiguration config) + { + throw new NotImplementedException(); + } + } + } +} + +namespace NServiceBus +{ + using System; + + public static partial class SerializationConfigExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "To use a custom serializer derive from SerializationDefinition and provide a factory method for creating the serializer instance.")] + public static void UseSerialization(this EndpointConfiguration config, Type serializerType) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Serialization +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "To use a custom serializer derive from SerializationDefinition and provide a factory method for creating the serializer instance.")] + public abstract class ConfigureSerialization + { + } +} + +namespace NServiceBus.Serializers.Json +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Built-in serializers are internal. Switch to an alternative (e.g. Json.net) or copy the serializer code.")] + public class JsonMessageSerializer + { + } +} + +namespace NServiceBus.Serializers.XML +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Built-in serializers are internal. Switch to an alternative (e.g. XmlSerializer) or copy the serializer code.")] + public class XmlMessageSerializer + { + } +} + +namespace NServiceBus.Transports.Msmq +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer available, see the documentation for native sends for alternative solutions.")] + public class MsmqMessageSender + { + } +} + +namespace NServiceBus.Transports.Msmq.Config +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "No longer available, see the documentation for native sends for alternative solutions.")] + public class MsmqSettings + { + } +} + +public static class ConfigureHandlerSettings +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static void InitializeHandlerProperty(this EndpointConfiguration config, string property, object value) + { + } +} + + +namespace NServiceBus.ObjectBuilder +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public interface IComponentConfig + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public interface IComponentConfig + { + } +} + +namespace NServiceBus.ObjectBuilder +{ + using System; + using System.Linq.Expressions; + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static class IConfigureComponentObsoleteExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static IConfigureComponents ConfigureProperty(this IConfigureComponents config, Expression> property, object value) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static IConfigureComponents ConfigureProperty(this IConfigureComponents config, string propertyName, object value) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Settings +{ + using System; + using System.Linq.Expressions; + using ObjectBuilder; + + public partial class SettingsHolder + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public void ApplyTo(IComponentConfig config) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public void ApplyTo(Type componentType, IComponentConfig config) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Set(string key, object value)")] + public void SetProperty(Expression> property, object value) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "Set(string key, object value)")] + public void SetPropertyDefault(Expression> property, object value) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Settings +{ + using System; + using ObjectBuilder; + + public static class ReadOnlySettingsExtensions + { + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static void ApplyTo(IComponentConfig config) + { + throw new NotImplementedException(); + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "Setting property values explicitly is no longer supported via this API. Use `.ConfigureComponent(b=> new MyMessageHandler(){ MyProperty = X})` to get full control over handler creation.")] + public static void ApplyTo(Type componentType, IComponentConfig config) + { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.DataBus +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "DataBusExtensions")] + public class DataBusExtentions + { + } + + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + ReplacementTypeOrMember = "DataBusExtensions")] + public class DataBusExtentions + { + } +} + +namespace NServiceBus.Features +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "FirstLevelRetries is no longer a separate feature. Please use endpointConfiguration.Recoverability().Immediate(cfg => cfg.NumberOfRetries(0)); to disable Immediate Retries.")] + public class FirstLevelRetries : Feature + { + internal FirstLevelRetries() + { + } + protected internal override void Setup(FeatureConfigurationContext context) + { + throw new System.NotImplementedException(); + } + } +} + +namespace NServiceBus.Features +{ + [ObsoleteEx( + RemoveInVersion = "7.0", + TreatAsErrorFromVersion = "6.0", + Message = "SecondLevelRetries is no longer a separate feature. Please use endpointConfiguration.Recoverability().Delayed(cfg => cfg.NumberOfRetries(0)) to disable Delayed Retries.")] + public class SecondLevelRetries : Feature + { + internal SecondLevelRetries() + { + } + + protected internal override void Setup(FeatureConfigurationContext context) + { + } + } +} + + + + diff --git a/src/NServiceBus.Core/packages.config b/src/NServiceBus.Core/packages.config index b685d00ee67..3a40c537d36 100644 --- a/src/NServiceBus.Core/packages.config +++ b/src/NServiceBus.Core/packages.config @@ -1,11 +1,12 @@  - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.Deploy/NServiceBus.Deploy.csproj b/src/NServiceBus.Deploy/NServiceBus.Deploy.csproj deleted file mode 100644 index 0abfbf20e41..00000000000 --- a/src/NServiceBus.Deploy/NServiceBus.Deploy.csproj +++ /dev/null @@ -1,68 +0,0 @@ - - - - - Debug - AnyCPU - {C0B7BF34-6EBC-4B03-8F46-5419555A3DB4} - Library - Properties - NServiceBus.Deploy - NServiceBus.Deploy - v4.5 - 512 - cdd4280a - ..\ - False - NServiceBus - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - \ No newline at end of file diff --git a/src/NServiceBus.Deploy/packages.config b/src/NServiceBus.Deploy/packages.config deleted file mode 100644 index 8f56639a217..00000000000 --- a/src/NServiceBus.Deploy/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/ConfigureMsmqTransport.cs b/src/NServiceBus.Msmq.AcceptanceTests/ConfigureMsmqTransport.cs new file mode 100644 index 00000000000..146f9dafea4 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/ConfigureMsmqTransport.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Messaging; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.Configuration.AdvanceExtensibility; +using NServiceBus.Transport; + +public class ConfigureEndpointMsmqTransport : IConfigureEndpointTestExecution +{ + public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings) + { + queueBindings = configuration.GetSettings().Get(); + var connectionString = settings.Get("Transport.ConnectionString"); + configuration.UseTransport().ConnectionString(connectionString); + return Task.FromResult(0); + } + + public Task Cleanup() + { + var allQueues = MessageQueue.GetPrivateQueuesByMachine("localhost"); + var queuesToBeDeleted = new List(); + + foreach (var messageQueue in allQueues) + { + using (messageQueue) + { + if (queueBindings.ReceivingAddresses.Any(ra => + { + var indexOfAt = ra.IndexOf("@", StringComparison.Ordinal); + if (indexOfAt >= 0) + { + ra = ra.Substring(0, indexOfAt); + } + return messageQueue.QueueName.StartsWith(@"private$\" + ra, StringComparison.OrdinalIgnoreCase); + })) + { + queuesToBeDeleted.Add(messageQueue.Path); + } + } + } + + foreach (var queuePath in queuesToBeDeleted) + { + try + { + MessageQueue.Delete(queuePath); + Console.WriteLine("Deleted '{0}' queue", queuePath); + } + catch (Exception) + { + Console.WriteLine("Could not delete queue '{0}'", queuePath); + } + } + + MessageQueue.ClearConnectionCache(); + + return Task.FromResult(0); + } + + QueueBindings queueBindings; +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/DefaultPublisher.cs b/src/NServiceBus.Msmq.AcceptanceTests/DefaultPublisher.cs new file mode 100644 index 00000000000..d64e46fb2e5 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/DefaultPublisher.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting.Support; + using Config.ConfigurationSource; + + public class DefaultPublisher : IEndpointSetupTemplate + { + public Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) + { + return new DefaultServer().GetConfiguration(runDescriptor, endpointConfiguration, configSource, configurationBuilderCustomization); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/DefaultServer.cs b/src/NServiceBus.Msmq.AcceptanceTests/DefaultServer.cs new file mode 100644 index 00000000000..40290540b26 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/DefaultServer.cs @@ -0,0 +1,105 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using AcceptanceTesting.Customization; + using AcceptanceTesting.Support; + using Config.ConfigurationSource; + using Features; + using Hosting.Helpers; + using ObjectBuilder; + + public class DefaultServer : IEndpointSetupTemplate + { + List typesToInclude; + + public DefaultServer() + { + typesToInclude = new List(); + } + + public DefaultServer(List typesToInclude) + { + this.typesToInclude = typesToInclude; + } + + public Task GetConfiguration(RunDescriptor runDescriptor, EndpointCustomizationConfiguration endpointConfiguration, IConfigurationSource configSource, Action configurationBuilderCustomization) + { + var types = GetTypesScopedByTestClass(endpointConfiguration); + + typesToInclude.AddRange(types); + + var builder = new EndpointConfiguration(endpointConfiguration.EndpointName); + + builder.TypesToIncludeInScan(typesToInclude); + builder.CustomConfigurationSource(configSource); + builder.EnableInstallers(); + + builder.DisableFeature(); + builder.Recoverability() + .Delayed(delayed => delayed.NumberOfRetries(0)) + .Immediate(immediate => immediate.NumberOfRetries(0)); + + builder.RegisterComponents(r => { RegisterInheritanceHierarchyOfContextOnContainer(runDescriptor, r); }); + + builder.UsePersistence(); + + configurationBuilderCustomization(builder); + + return Task.FromResult(builder); + } + + static void RegisterInheritanceHierarchyOfContextOnContainer(RunDescriptor runDescriptor, IConfigureComponents r) + { + var type = runDescriptor.ScenarioContext.GetType(); + while (type != typeof(object)) + { + r.RegisterSingleton(type, runDescriptor.ScenarioContext); + type = type.BaseType; + } + } + + static IEnumerable GetTypesScopedByTestClass(EndpointCustomizationConfiguration endpointConfiguration) + { + var assemblies = new AssemblyScanner().GetScannableAssemblies(); + + var types = assemblies.Assemblies + //exclude all test types by default + .Where(a => + { + var references = a.GetReferencedAssemblies(); + + return references.All(an => an.Name != "nunit.framework"); + }) + .SelectMany(a => a.GetTypes()); + + + types = types.Union(GetNestedTypeRecursive(endpointConfiguration.BuilderType.DeclaringType, endpointConfiguration.BuilderType)); + + types = types.Union(endpointConfiguration.TypesToInclude); + + return types.Where(t => !endpointConfiguration.TypesToExclude.Contains(t)).ToList(); + } + + static IEnumerable GetNestedTypeRecursive(Type rootType, Type builderType) + { + if (rootType == null) + { + throw new InvalidOperationException("Make sure you nest the endpoint infrastructure inside the TestFixture as nested classes"); + } + + yield return rootType; + + if (typeof(IEndpointConfigurationFactory).IsAssignableFrom(rootType) && rootType != builderType) + yield break; + + foreach (var nestedType in rootType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SelectMany(t => GetNestedTypeRecursive(t, builderType))) + { + yield return nestedType; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/FakePromotableResourceManager.cs b/src/NServiceBus.Msmq.AcceptanceTests/FakePromotableResourceManager.cs new file mode 100644 index 00000000000..af0b64467b4 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/FakePromotableResourceManager.cs @@ -0,0 +1,54 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Transactions; + + public class FakePromotableResourceManager : IPromotableSinglePhaseNotification, IEnlistmentNotification + { + public static Guid ResourceManagerId = Guid.Parse("6f057e24-a0d8-4c95-b091-b8dc9a916fa4"); + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + preparingEnlistment.Prepared(); + } + + public void Commit(Enlistment enlistment) + { + enlistment.Done(); + } + + public void Rollback(Enlistment enlistment) + { + enlistment.Done(); + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + + + public void Initialize() + { + } + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + singlePhaseEnlistment.Committed(); + } + + public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) + { + singlePhaseEnlistment.Done(); + } + + public byte[] Promote() + { + return TransactionInterop.GetTransmitterPropagationToken(new CommittableTransaction()); + + } + + + } + +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/NServiceBus.Msmq.AcceptanceTests.csproj b/src/NServiceBus.Msmq.AcceptanceTests/NServiceBus.Msmq.AcceptanceTests.csproj new file mode 100644 index 00000000000..704fb42a33b --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/NServiceBus.Msmq.AcceptanceTests.csproj @@ -0,0 +1,111 @@ + + + + + Debug + AnyCPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02} + Library + Properties + NServiceBus.AcceptanceTests + NServiceBus.Msmq.AcceptanceTests + v4.6 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + full + false + bin\Release\ + TRACE + prompt + 4 + true + + + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {758357f6-cd31-4337-80c4-ba377fc257af} + NServiceBus.AcceptanceTesting + + + {dd48b2d0-e996-412d-9157-821ed8b17a9d} + NServiceBus.Core + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/NServiceBusAcceptanceTest.cs b/src/NServiceBus.Msmq.AcceptanceTests/NServiceBusAcceptanceTest.cs new file mode 100644 index 00000000000..1021f668cde --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/NServiceBusAcceptanceTest.cs @@ -0,0 +1,33 @@ +namespace NServiceBus.AcceptanceTests +{ + using System.Linq; + using AcceptanceTesting.Customization; + using NUnit.Framework; + + /// + /// Base class for all the NSB test that sets up our conventions + /// + [TestFixture] + public abstract class NServiceBusAcceptanceTest + { + [SetUp] + public void SetUp() + { + Conventions.EndpointNamingConvention = t => + { + var classAndEndpoint = t.FullName.Split('.').Last(); + + var testName = classAndEndpoint.Split('+').First(); + + testName = testName.Replace("When_", ""); + + var endpointBuilder = classAndEndpoint.Split('+').Last(); + + testName = System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(testName); + testName = testName.Replace("_", ""); + + return testName +"."+ endpointBuilder; + }; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehavior.cs b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehavior.cs new file mode 100644 index 00000000000..ef21c65c240 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehavior.cs @@ -0,0 +1,54 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using Pipeline; + using Transport; + + class SubscriptionBehavior : IBehavior where TContext : ScenarioContext + { + Action action; + TContext scenarioContext; + + public SubscriptionBehavior(Action action, TContext scenarioContext) + { + this.action = action; + this.scenarioContext = scenarioContext; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + await next(context).ConfigureAwait(false); + var subscriptionMessageType = GetSubscriptionMessageTypeFrom(context.Message); + if (subscriptionMessageType != null) + { + string returnAddress; + if (!context.Message.Headers.TryGetValue(Headers.SubscriberTransportAddress, out returnAddress)) + { + context.Message.Headers.TryGetValue(Headers.ReplyToAddress, out returnAddress); + } + action(new SubscriptionEventArgs + { + MessageType = subscriptionMessageType, + SubscriberReturnAddress = returnAddress + }, scenarioContext); + } + } + + static string GetSubscriptionMessageTypeFrom(IncomingMessage msg) + { + return (from header in msg.Headers where header.Key == Headers.SubscriptionMessageType select header.Value).FirstOrDefault(); + } + + internal class Registration : RegisterStep + { + public Registration() + : base("SubscriptionBehavior", typeof(SubscriptionBehavior), "So we can get subscription events") + { + InsertBeforeIfExists("ProcessSubscriptionRequests"); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehaviorExtensions.cs b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehaviorExtensions.cs new file mode 100644 index 00000000000..aba1d311d97 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionBehaviorExtensions.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using AcceptanceTesting; + + static class SubscriptionBehaviorExtensions + { + public static void OnEndpointSubscribed(this EndpointConfiguration b, Action action) where TContext : ScenarioContext + { + b.Pipeline.Register.Registration>(); + + b.RegisterComponents(c => c.ConfigureComponent(builder => + { + var context = builder.Build(); + return new SubscriptionBehavior(action, context); + }, DependencyLifecycle.InstancePerCall)); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionEventArgs.cs b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionEventArgs.cs new file mode 100644 index 00000000000..5c6fe1d907a --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/SubscriptionEventArgs.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.AcceptanceTests +{ + public class SubscriptionEventArgs + { + /// + /// The address of the subscriber. + /// + public string SubscriberReturnAddress { get; set; } + + /// + /// The type of message the client subscribed to. + /// + public string MessageType { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/UnicastRoutingExtensions.cs b/src/NServiceBus.Msmq.AcceptanceTests/UnicastRoutingExtensions.cs new file mode 100644 index 00000000000..573141b775c --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/UnicastRoutingExtensions.cs @@ -0,0 +1,22 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System; + using System.Collections.Generic; + using Configuration.AdvanceExtensibility; + using NServiceBus.Routing; + using NServiceBus.Routing.MessageDrivenSubscriptions; + + static class UnicastRoutingExtensions + { + public static void RegisterPublisher(this RoutingSettings config, Type eventType, string publisherEndpoint) + { + config.GetSettings().GetOrCreate().AddOrReplacePublishers(Guid.NewGuid().ToString(), + new List { new PublisherTableEntry(eventType, PublisherAddress.CreateFromEndpointName(publisherEndpoint))}); + } + + public static void RegisterEndpointInstances(this RoutingSettings config, params EndpointInstance[] instances) + { + config.GetSettings().GetOrCreate().AddOrReplaceInstances(Guid.NewGuid().ToString(), instances); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_Audit_OverrideTimeToBeReceived_set_and_native_receive_tx.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_Audit_OverrideTimeToBeReceived_set_and_native_receive_tx.cs new file mode 100644 index 00000000000..1557c62a1c4 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_Audit_OverrideTimeToBeReceived_set_and_native_receive_tx.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using AcceptanceTesting; + using Config; + using NUnit.Framework; + + public class When_Audit_OverrideTimeToBeReceived_set_and_native_receive_tx : NServiceBusAcceptanceTest + { + [Test] + public void Endpoint_should_not_start_and_show_error() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Run(); + }, Throws.InnerException.InnerException.Message.Contains("Setting a custom OverrideTimeToBeReceived for audits is not supported on transactional MSMQ.")); + } + + public class Context : ScenarioContext + { + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport() + .Transactions(TransportTransactionMode.ReceiveOnly); + }) + .WithConfig(c => c.OverrideTimeToBeReceived = TimeSpan.FromHours(1)); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceivedOnForwardedMessages_set_and_tx_scope_receives.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceivedOnForwardedMessages_set_and_tx_scope_receives.cs new file mode 100644 index 00000000000..fe3bcda05a7 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceivedOnForwardedMessages_set_and_tx_scope_receives.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using AcceptanceTesting; + using Config; + using NUnit.Framework; + + public class When_TimeToBeReceivedOnForwardedMessages_set_and_tx_scope_receives : NServiceBusAcceptanceTest + { + [Test] + public void Endpoint_should_not_start_and_show_error() + { + Assert.That(async () => + { + await Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Run(); + }, Throws.InnerException.InnerException.Message.Contains("Setting a custom TimeToBeReceivedOnForwardedMessages is not supported on transactional MSMQ.")); + } + + public class Context : ScenarioContext + { + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport() + .Transactions(TransportTransactionMode.TransactionScope); + }) + .WithConfig(c => c.TimeToBeReceivedOnForwardedMessages = TimeSpan.FromHours(1)); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceived_set_and_native_receivetransaction.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceived_set_and_native_receivetransaction.cs new file mode 100644 index 00000000000..816a52a8c6b --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_TimeToBeReceived_set_and_native_receivetransaction.cs @@ -0,0 +1,63 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using NUnit.Framework; + + public class When_TimeToBeReceived_set_and_native_receivetransaction : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_on_send() + { + var exception = Assert.ThrowsAsync(async () => + await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => await session.SendLocal(new MyMessage()))) + .Done(c => c.HandlerInvoked) + .Run()) + .ExpectFailedMessages(); + + Assert.AreEqual(1, exception.FailedMessages.Count); + StringAssert.EndsWith( + "Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ.", + exception.FailedMessages.Single().Exception.Message); + } + + public class Context : ScenarioContext + { + public bool HandlerInvoked { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport().Transactions(TransportTransactionMode.SendsAtomicWithReceive); + }); + } + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Context.HandlerInvoked = true; + return context.SendLocal(new MyTimeToBeReceivedMessage()); + } + } + } + + public class MyMessage : IMessage + { + } + + [TimeToBeReceived("00:01:00")] + public class MyTimeToBeReceivedMessage : IMessage + { + } + } +} diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_a_corrupted_message_is_received.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_a_corrupted_message_is_received.cs new file mode 100644 index 00000000000..6a8b0bcb641 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_a_corrupted_message_is_received.cs @@ -0,0 +1,96 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Linq; + using System.Messaging; + using System.Text; + using System.Threading.Tasks; + using AcceptanceTesting; + using Logging; + using NUnit.Framework; + + public class When_a_corrupted_message_is_received : NServiceBusAcceptanceTest + { + const string errorQueue = @".\private$\errorQueueForCorruptedMessages"; + + [TestCase(TransportTransactionMode.TransactionScope)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.None)] + public async Task Should_move_it_to_the_error_queue(TransportTransactionMode transactionMode) + { + DeleteQueue(); + try + { + await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UseTransport() + .Transactions(transactionMode); + }); + b.When((session, c) => + { + var endpoint = AcceptanceTesting.Customization.Conventions.EndpointNamingConvention(typeof(Endpoint)); + + var inputQueue = $@".\private$\{endpoint}"; + + using (var queue = new MessageQueue(inputQueue)) + using (var message = new Message()) + { + message.Extension = Encoding.UTF8.GetBytes(" c.Logs.Any(l => l.Level == LogLevel.Error)) + .Run(); + Assert.True(MessageExistsInErrorQueue(), "The message should have been moved to the error queue"); + } + finally + { + DeleteQueue(); + } + } + + static void DeleteQueue() + { + if (MessageQueue.Exists(errorQueue)) + { + MessageQueue.Delete(errorQueue); + } + } + + static bool MessageExistsInErrorQueue() + { + if (!MessageQueue.Exists(errorQueue)) + { + return false; + } + using (var queue = new MessageQueue(errorQueue)) + using (var message = queue.Receive(TimeSpan.FromSeconds(5))) + { + return message != null; + } + } + + public class Context : ScenarioContext + { + } + + + public class Endpoint : EndpointConfigurationBuilder + { + public Endpoint() + { + EndpointSetup(c => + { + c.SendFailedMessagesTo("errorQueueForCorruptedMessages"); + }); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_customizing_scope_isolation_level.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_customizing_scope_isolation_level.cs new file mode 100644 index 00000000000..8e7f7e9deaf --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_customizing_scope_isolation_level.cs @@ -0,0 +1,62 @@ +namespace NServiceBus.AcceptanceTests +{ + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_customizing_scope_isolation_level : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_honor_configured_level() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.Done) + .Run(); + + Assert.True(context.AmbientTransactionPresent, "There should be a ambient transaction present"); + Assert.AreEqual(context.IsolationLevel, IsolationLevel.RepeatableRead, "There should be a ambient transaction present"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool AmbientTransactionPresent { get; set; } + public IsolationLevel IsolationLevel { get; set; } + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup(c => + { + c.UseTransport() + .Transactions(TransportTransactionMode.TransactionScope) + .TransactionScopeOptions(isolationLevel: IsolationLevel.RepeatableRead); + }); + } + + class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (Transaction.Current != null) + { + Context.AmbientTransactionPresent = Transaction.Current != null; + Context.IsolationLevel = Transaction.Current.IsolationLevel; + } + Context.Done = true; + + return Task.FromResult(0); + } + } + } + + class MyMessage : IMessage + { } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_a_command.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_a_command.cs new file mode 100644 index 00000000000..3c20268cf57 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_a_command.cs @@ -0,0 +1,184 @@ +namespace NServiceBus.AcceptanceTests +{ + using System.Threading.Tasks; + using AcceptanceTesting.Customization; + using AcceptanceTesting; + using NServiceBus.Routing; + using NUnit.Framework; + using Routing; + using Settings; + + public class When_distributing_a_command : NServiceBusAcceptanceTest + { + const int numberOfMessagesToSendPerEndpoint = 20; + static string ReceiverAEndpoint => Conventions.EndpointNamingConvention(typeof(ReceiverA)); + static string ReceiverBEndpoint => Conventions.EndpointNamingConvention(typeof(ReceiverB)); + + [Test] + public async Task Should_round_robin() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When((session, c) => session.Send(new RequestA()))) + .WithEndpoint(b => b.CustomConfig(c => + { + c.MakeInstanceUniquelyAddressable("1"); + })) + .WithEndpoint(b => b.CustomConfig(c => + { + c.MakeInstanceUniquelyAddressable("2"); + })) + .WithEndpoint(b => b.CustomConfig(c => + { + c.MakeInstanceUniquelyAddressable("1"); + })) + .WithEndpoint(b => b.CustomConfig(c => + { + c.MakeInstanceUniquelyAddressable("2"); + })) + .Done(c => c.MessagesReceivedPerEndpoint == numberOfMessagesToSendPerEndpoint) + .Run(); + + Assert.That(context.ReceiverA1TimesCalled, Is.EqualTo(10)); + Assert.That(context.ReceiverA2TimesCalled, Is.EqualTo(10)); + Assert.That(context.ReceiverB1TimesCalled, Is.EqualTo(10)); + Assert.That(context.ReceiverB2TimesCalled, Is.EqualTo(10)); + } + + public class Context : ScenarioContext + { + public int MessagesReceivedPerEndpoint { get; set; } + public int ReceiverA1TimesCalled { get; set; } + public int ReceiverA2TimesCalled { get; set; } + public int ReceiverB1TimesCalled { get; set; } + public int ReceiverB2TimesCalled { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + var routing = c.UseTransport().Routing(); + routing.RouteToEndpoint(typeof(RequestA), ReceiverAEndpoint); + routing.RouteToEndpoint(typeof(RequestB), ReceiverBEndpoint); + + routing.RegisterEndpointInstances( + new EndpointInstance(ReceiverAEndpoint, "1"), + new EndpointInstance(ReceiverAEndpoint, "2"), + new EndpointInstance(ReceiverBEndpoint, "1"), + new EndpointInstance(ReceiverBEndpoint, "2") + ); + }); + } + + public class ResponseHandler : + IHandleMessages, + IHandleMessages + { + Context testContext; + + public ResponseHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(ResponseA message, IMessageHandlerContext context) + { + switch (message.EndpointInstance) + { + case "1": + testContext.ReceiverA1TimesCalled++; + break; + case "2": + testContext.ReceiverA2TimesCalled++; + break; + } + + return context.Send(new RequestB()); + } + + public Task Handle(ResponseB message, IMessageHandlerContext context) + { + switch (message.EndpointInstance) + { + case "1": + testContext.ReceiverB1TimesCalled++; + break; + case "2": + testContext.ReceiverB2TimesCalled++; + break; + } + + testContext.MessagesReceivedPerEndpoint++; + if (testContext.MessagesReceivedPerEndpoint < numberOfMessagesToSendPerEndpoint) + { + return context.Send(new RequestA()); + } + + return Task.CompletedTask; + } + } + } + + public class ReceiverA : EndpointConfigurationBuilder + { + public ReceiverA() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public ReadOnlySettings Settings { get; set; } + + public Task Handle(RequestA message, IMessageHandlerContext context) + { + return context.Reply(new ResponseA + { + EndpointInstance = Settings.Get("EndpointInstanceDiscriminator") + }); + } + } + } + + public class ReceiverB : EndpointConfigurationBuilder + { + public ReceiverB() + { + EndpointSetup(); + } + + public class MyMessageHandler : IHandleMessages + { + public ReadOnlySettings Settings { get; set; } + + public Task Handle(RequestB message, IMessageHandlerContext context) + { + return context.Reply(new ResponseB + { + EndpointInstance = Settings.Get("EndpointInstanceDiscriminator") + }); + } + } + } + + public class RequestA : ICommand + { + } + + public class RequestB : ICommand + { + } + + public class ResponseA : IMessage + { + public string EndpointInstance { get; set; } + } + + public class ResponseB : IMessage + { + public string EndpointInstance { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_an_event.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_an_event.cs new file mode 100644 index 00000000000..d4541e96ab9 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_distributing_an_event.cs @@ -0,0 +1,127 @@ +namespace NServiceBus.AcceptanceTests +{ + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting.Customization; + using AcceptanceTesting; + using Features; + using NUnit.Framework; + + public class When_distributing_an_event : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + static string SubscriberEndpoint => Conventions.EndpointNamingConvention(typeof(Subscriber)); + + [Test] + public async Task Should_round_robin() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscriptions == 2, async (session, c) => + { + for (var i = 0; i < 10; i++) + { + await session.Publish(new MyEvent()).ConfigureAwait(false); + } + })) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.Receiver1TimesCalled + c.Receiver2TimesCalled>= 10) + .Run(); + + Assert.AreEqual(5, context.Receiver1TimesCalled); + Assert.AreEqual(5, context.Receiver2TimesCalled); + } + + public class Context : ScenarioContext + { + public int Subscriptions { get; set; } + + int receiver1TimesCalled; + public int Receiver1TimesCalled => receiver1TimesCalled; + public void OnReceived1() + { + Interlocked.Increment(ref receiver1TimesCalled); + } + + int receiver2TimesCalled; + public int Receiver2TimesCalled => receiver2TimesCalled; + public void OnReceived2() + { + Interlocked.Increment(ref receiver2TimesCalled); + } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.DisableFeature(); + c.OnEndpointSubscribed((s, context) => + { + context.Subscriptions++; + }); + c.LimitMessageProcessingConcurrencyTo(1); + }); + } + } + + //Used only to generate a name + public class Subscriber : EndpointConfigurationBuilder + { + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => + { + c.OverrideLocalAddress(SubscriberEndpoint + "-1"); + var transport = c.UseTransport(); + transport.Routing().RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }).CustomEndpointName(SubscriberEndpoint); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.OnReceived1(); + return Task.FromResult(0); + } + } + } + + public class Subscriber2 : EndpointConfigurationBuilder + { + public Subscriber2() + { + EndpointSetup(c => + { + c.OverrideLocalAddress(SubscriberEndpoint + "-2"); + var transport = c.UseTransport(); + transport.Routing().RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }).CustomEndpointName(SubscriberEndpoint); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + Context.OnReceived2(); + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_not_using_instance_mapping_file.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_not_using_instance_mapping_file.cs new file mode 100644 index 00000000000..d9475686671 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_not_using_instance_mapping_file.cs @@ -0,0 +1,65 @@ +namespace NServiceBus.AcceptanceTests +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using NUnit.Framework; + + public class When_not_using_instance_mapping_file : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_use_logical_endpoint_name_as_address() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(c => c.Send(new Message()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Run(); + + Assert.That(context.ReceivedMessage, Is.True); + } + + public class Context : ScenarioContext + { + public bool ReceivedMessage { get; set; } + } + + public class SenderWithoutMappingFile : EndpointConfigurationBuilder + { + public SenderWithoutMappingFile() + { + EndpointSetup(c => c.UseTransport().Routing() + // only configure logical endpoint + .RouteToEndpoint(typeof(Message), Conventions.EndpointNamingConvention(typeof(ReceiverWithoutMappingFile)))); + } + } + + public class ReceiverWithoutMappingFile : EndpointConfigurationBuilder + { + public ReceiverWithoutMappingFile() + { + EndpointSetup(); + } + + public class MessageHandler : IHandleMessages + { + Context testContext; + + public MessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_overriding_local_address.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_overriding_local_address.cs new file mode 100644 index 00000000000..ae4ba9ea095 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_overriding_local_address.cs @@ -0,0 +1,95 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.IO; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using NUnit.Framework; + + public class When_overriding_local_address : NServiceBusAcceptanceTest + { + public static string ReceiverEndpointName => Conventions.EndpointNamingConvention(typeof(Receiver)); + public static string ReceiverQueueName => "q_" + ReceiverEndpointName; + + static string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, nameof(When_overriding_local_address) + ".xml"); + + [SetUp] + public void SetupMappingFile() + { + File.WriteAllText(mappingFilePath, +$@" + + + +"); + } + + [TearDown] + public void DeleteMappingFile() + { + File.Delete(mappingFilePath); + } + + [Test] + public async Task Should_use_provided_instance_mapping() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(c => c.Send(new Message()))) + .WithEndpoint() + .Done(c => c.ReceivedMessage) + .Run(); + + Assert.That(context.ReceivedMessage, Is.True); + } + + public class Context : ScenarioContext + { + public bool ReceivedMessage { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + var transport = c.UseTransport(); + var routing = transport.Routing(); + + // only configure logical endpoint in routing + routing.RouteToEndpoint(typeof(Message), ReceiverEndpointName); + routing.InstanceMappingFile().FilePath(mappingFilePath); + }); + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => c.OverrideLocalAddress(ReceiverQueueName)); + } + + public class MessageHandler : IHandleMessages + { + Context testContext; + + public MessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + testContext.ReceivedMessage = true; + return Task.FromResult(0); + } + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_publishing.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_publishing.cs new file mode 100644 index 00000000000..98ac9759375 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_publishing.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Features; + using Persistence.Legacy; + using NUnit.Framework; + + public class When_using_subscription_store : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_be_delivered_to_all_subscribers() + { + var ctx = await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, (session, c) => + { + c.AddTrace("Both subscribers is subscribed, going to publish MyEvent"); + return session.Publish(new MyEvent()); + }) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + if (context.HasNativePubSubSupport) + { + context.Subscribed = true; + context.AddTrace("Subscriber1 is now subscribed (at least we have asked the broker to be subscribed)"); + } + else + { + context.AddTrace("Subscriber1 has now asked to be subscribed to MyEvent"); + } + })) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(10)); + + Assert.IsTrue(ctx.GotTheEvent); + } + + public class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool Subscribed { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.OnEndpointSubscribed((s, context) => + { + context.Subscribed = true; + context.AddTrace("Subscriber1 is now subscribed"); + }); + b.DisableFeature(); + b.UsePersistence(); + }); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context TestContext { get; set; } + + public Task Handle(MyEvent message, IMessageHandlerContext context) + { + TestContext.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + [Serializable] + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_publishing_with_authorizer.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_publishing_with_authorizer.cs new file mode 100644 index 00000000000..1f3d49e69da --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_publishing_with_authorizer.cs @@ -0,0 +1,137 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Features; + using Pipeline; + using NUnit.Framework; + + public class When_publishing_with_authorizer : NServiceBusAcceptanceTest + { + + [Test] + public async Task Should_only_deliver_to_authorized() + { + var context = await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscriber1Subscribed && c.Subscriber2Subscribed, (session, c) => session.Publish(new MyEvent())) + ) + .WithEndpoint(b => b.When(async (session, c) => + { + await session.Subscribe(); + })) + .WithEndpoint(b => b.When(async (session, c) => + { + await session.Subscribe(); + })) + .Done(c => + c.Subscriber1GotTheEvent && + c.DeclinedSubscriber2) + .Run(TimeSpan.FromSeconds(10)); + + Assert.True(context.Subscriber1GotTheEvent); + Assert.False(context.Subscriber2GotTheEvent); + } + + public class TestContext : ScenarioContext + { + public bool Subscriber1GotTheEvent { get; set; } + public bool Subscriber2GotTheEvent { get; set; } + public bool Subscriber1Subscribed { get; set; } + public bool Subscriber2Subscribed { get; set; } + public bool DeclinedSubscriber2 { get; set; } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.UseTransport().SubscriptionAuthorizer(Authorizer); + b.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberReturnAddress.Contains("Subscriber1")) + { + context.Subscriber1Subscribed = true; + } + + if (s.SubscriberReturnAddress.Contains("Subscriber2")) + { + context.Subscriber2Subscribed = true; + } + }); + b.DisableFeature(); + }); + } + + bool Authorizer(IIncomingPhysicalMessageContext context) + { + var isFromSubscriber1 = context + .MessageHeaders["NServiceBus.SubscriberEndpoint"] + .EndsWith("Subscriber1"); + if (!isFromSubscriber1) + { + var testContext = (TestContext)ScenarioContext; + testContext.DeclinedSubscriber2 = true; + } + return isFromSubscriber1; + } + } + + public class Subscriber1 : EndpointConfigurationBuilder + { + public Subscriber1() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + TestContext context; + + public MyEventHandler(TestContext context) + { + this.context = context; + } + + public Task Handle(MyEvent message, IMessageHandlerContext handlerContext) + { + context.Subscriber1GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class Subscriber2 : EndpointConfigurationBuilder + { + public Subscriber2() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + TestContext context; + + public MyEventHandler(TestContext context) + { + this.context = context; + } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext handlerContext) + { + context.Subscriber2GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_control_message_with_body.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_control_message_with_body.cs new file mode 100644 index 00000000000..550b4812f11 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_control_message_with_body.cs @@ -0,0 +1,116 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Extensibility; + using Features; + using NServiceBus.Routing; + using NUnit.Framework; + using Pipeline; + using Transport; + + public class When_receiving_control_message_with_body : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_treat_it_as_control_message() + { + var context = await Scenario.Define() + .WithEndpoint() + .Done(c => c.ControlMessageProcessed || c.ControlMessageFailed) + .Run(); + + Assert.IsTrue(context.ControlMessageProcessed); + } + + public class Context : ScenarioContext + { + public bool ControlMessageProcessed { get; set; } + public bool ControlMessageFailed { get; set; } + } + + class V33ControlMessageSimulator : Feature + { + protected override void Setup(FeatureConfigurationContext context) + { + context.RegisterStartupTask(b => new V33ControlMessageSimulatorTask(b.Build())); + } + + class V33ControlMessageSimulatorTask : FeatureStartupTask + { + readonly IDispatchMessages dispatcher; + + public V33ControlMessageSimulatorTask(IDispatchMessages dispatcher) + { + this.dispatcher = dispatcher; + } + + protected override Task OnStart(IMessageSession session) + { + // Simulating a v3.3 control message + var body = Encoding.UTF8.GetBytes(@" + + + 5 + +"); + var outgoingMessage = new OutgoingMessage("0dac4ec2-a0ed-42ee-a306-ff191322d59d\\47283703", new Dictionary + { + {"NServiceBus.ControlMessage", "True"}, + {"NServiceBus.ReturnMessage.ErrorCode", "5"}, + {"NServiceBus.ContentType", "text/xml"} + }, body); + + var endpoint = Conventions.EndpointNamingConvention(typeof(TestingEndpoint)); + return dispatcher.Dispatch(new TransportOperations(new TransportOperation(outgoingMessage, new UnicastAddressTag(endpoint))), new TransportTransaction(), new ContextBag()); + } + + protected override Task OnStop(IMessageSession session) + { + return Task.FromResult(0); + } + } + } + + public class TestingEndpoint : EndpointConfigurationBuilder + { + public TestingEndpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport(); + config.Pipeline.Register("AssertBehavior", new AssertBehavior((Context)ScenarioContext), "Asserts message was processed without any failures"); + config.EnableFeature(); + }); + } + + public class AssertBehavior : IBehavior + { + public AssertBehavior(Context testContext) + { + this.testContext = testContext; + } + + public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) + { + try + { + await next(context).ConfigureAwait(false); + } + catch (Exception) + { + testContext.ControlMessageFailed = true; + return; + } + + testContext.ControlMessageProcessed = true; + } + + Context testContext; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_with_dtc_disabled.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_with_dtc_disabled.cs new file mode 100644 index 00000000000..ccfd0e147cb --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_receiving_with_dtc_disabled.cs @@ -0,0 +1,68 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_receiving_with_dtc_disabled : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_not_escalate_a_single_durable_rm_to_dtc_tx() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(session => session.SendLocal(new MyMessage()))) + .Done(c => c.HandlerInvoked) + .Run(); + + Assert.AreEqual(Guid.Empty, context.DistributedIdentifierBefore, "No DTC tx should exist before enlistment"); + Assert.True(context.CanEnlistPromotable, "A promotable RM should be able to enlist"); + } + + public class Context : ScenarioContext + { + public bool HandlerInvoked { get; set; } + + public Guid DistributedIdentifierBefore { get; set; } + + public bool CanEnlistPromotable { get; set; } + } + + public class NonDTCEndpoint : EndpointConfigurationBuilder + { + public NonDTCEndpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport().Transactions(TransportTransactionMode.SendsAtomicWithReceive); + }); + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage messageThatIsEnlisted, IMessageHandlerContext context) + { + using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + Context.DistributedIdentifierBefore = Transaction.Current.TransactionInformation.DistributedIdentifier; + + Context.CanEnlistPromotable = Transaction.Current.EnlistPromotableSinglePhase(new FakePromotableResourceManager()); + + tx.Complete(); + } + + Context.HandlerInvoked = true; + + return Task.FromResult(0); + } + } + } + + public class MyMessage : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_replying_to_a_message_sent_via_a_distributor.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_replying_to_a_message_sent_via_a_distributor.cs new file mode 100644 index 00000000000..14d5332c643 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_replying_to_a_message_sent_via_a_distributor.cs @@ -0,0 +1,86 @@ +namespace NServiceBus.AcceptanceTests.ScaleOut +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using NServiceBus.Routing; + using NServiceBus.Routing.Legacy; + using NUnit.Framework; + using Routing; + + public class When_replying_to_a_message_sent_via_a_distributor : NServiceBusAcceptanceTest + { + static string ReceiverEndpoint => Conventions.EndpointNamingConvention(typeof(Receiver)); + + [Test] + public async Task Reply_address_should_be_set_to_that_specific_instance() + { + var context = await Scenario.Define() + .WithEndpoint() + .WithEndpoint(b => b.When(s => s.Send(new MyRequest()))) + .Done(c => c.ReplyToAddress != null) + .Run(); + + StringAssert.Contains("Distributor", context.ReplyToAddress); + } + + public class Context : ScenarioContext + { + public string ReplyToAddress { get; set; } + } + + public class Sender : EndpointConfigurationBuilder + { + public Sender() + { + EndpointSetup(c => + { + var routing = c.UseTransport().Routing(); + routing.RouteToEndpoint(typeof(MyRequest), ReceiverEndpoint); + routing.RegisterEndpointInstances(new EndpointInstance(ReceiverEndpoint, "XYZ")); + c.AddHeaderToAllOutgoingMessages("NServiceBus.Distributor.WorkerSessionId", "SomeID"); + }); + } + + public class MyResponseHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyResponse message, IMessageHandlerContext context) + { + Context.ReplyToAddress = context.MessageHeaders[Headers.ReplyToAddress]; + return Task.FromResult(0); + } + } + } + + public class Receiver : EndpointConfigurationBuilder + { + public Receiver() + { + EndpointSetup(c => + { + c.MakeInstanceUniquelyAddressable("XYZ"); + c.EnlistWithLegacyMSMQDistributor("Distributor", ReceiverEndpoint, 1); + }); + } + + + public class MyRequestHandler : IHandleMessages + { + public Task Handle(MyRequest message, IMessageHandlerContext context) + { + return context.Reply(new MyResponse()); + } + } + } + + public class MyRequest : IMessage + { + } + + public class MyResponse : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_setting_label_generator.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_setting_label_generator.cs new file mode 100644 index 00000000000..f4668061300 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_setting_label_generator.cs @@ -0,0 +1,116 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Messaging; + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + using Settings; + + public class When_setting_label_generator : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_receive_the_message_and_label() + { + DeleteAudit(); + try + { + await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => session.SendLocal(new MyMessage + { + Id = c.Id + }))) + .Done(c => c.WasCalled) + .Run(); + Assert.AreEqual("MyLabel", ReadMessageLabel()); + } + finally + { + DeleteAudit(); + } + } + + static void DeleteAudit() + { + if (MessageQueue.Exists(auditQueue)) + { + MessageQueue.Delete(auditQueue); + } + } + + static string ReadMessageLabel() + { + if (!MessageQueue.Exists(auditQueue)) + { + return null; + } + using (var queue = new MessageQueue(auditQueue)) + { + using (var message = queue.Receive(TimeSpan.FromSeconds(5))) + { + return message?.Label; + } + } + } + + const string auditQueue = @".\private$\labelAuditQueue"; + + public class Context : ScenarioContext + { + public bool WasCalled { get; set; } + public Guid Id { get; set; } + } + + public class Endpoint : EndpointConfigurationBuilder, IWantToRunBeforeConfigurationIsFinalized + { + public Endpoint() + { + if (initialized) + { + return; + } + initialized = true; + EndpointSetup(c => + { + c.AuditProcessedMessagesTo("labelAuditQueue"); + c.UseTransport().ApplyLabelToMessages(GetMessageLabel); + }); + } + + public void Run(SettingsHolder config) + { + } + + string GetMessageLabel(IReadOnlyDictionary headers) + { + return "MyLabel"; + } + + static bool initialized; + } + + [Serializable] + public class MyMessage : ICommand + { + public Guid Id { get; set; } + } + + public class MyMessageHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + if (Context.Id != message.Id) + { + return Task.FromResult(0); + } + + Context.WasCalled = true; + + return Task.FromResult(0); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_starting_up_the_endpoint.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_up_the_endpoint.cs new file mode 100644 index 00000000000..cb0bcc735c5 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_up_the_endpoint.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Linq; + using System.Security.Principal; + using System.Threading.Tasks; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_starting_up_the_endpoint : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_log_warning_if_queue_is_configured_with_anon_and_everyone_permissions() + { + var context = await Scenario.Define(c => { c.Id = Guid.NewGuid(); }) + .WithEndpoint(b => b.When((session, c) => Task.FromResult(0))) + .Run(); + + var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null).Translate(typeof(NTAccount)).ToString(); + var anonymous = new SecurityIdentifier(WellKnownSidType.AnonymousSid, null).Translate(typeof(NTAccount)).ToString(); + + var logItem = context.Logs.FirstOrDefault(item => item.Message.Contains($"[{everyone}] and [{anonymous}]")); + Assert.IsNotNull(logItem); + StringAssert.Contains($"is running with [{everyone}] and [{anonymous}] permissions. Consider setting appropriate permissions, if required by the organization", logItem.Message); + } + + class Context : ScenarioContext + { + public Guid Id { get; set; } + } + + class Endpoint : EndpointConfigurationBuilder + { + static bool initialized; + public Endpoint() + { + if (initialized) + { + return; + } + initialized = true; + EndpointSetup(c => + { + c.UseTransport(); + }); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_invalid_instance_mapping_file.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_invalid_instance_mapping_file.cs new file mode 100644 index 00000000000..1a84925e199 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_invalid_instance_mapping_file.cs @@ -0,0 +1,61 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.IO; + using System.Xml.Schema; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_starting_with_invalid_instance_mapping_file : NServiceBusAcceptanceTest + { + [SetUp] + public void SetupMappingFile() + { + // e.g. spelling error in endpoint: + File.WriteAllText(mappingFilePath, +@" + + + + +"); + } + + [TearDown] + public void DeleteMappingFile() + { + File.Delete(mappingFilePath); + } + + [Test] + public void Should_throw_at_startup() + { + var exception = Assert.ThrowsAsync(() => Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Run()); + + Assert.That(exception.InnerException.InnerException.Message, Does.Contain($"An error occurred while reading the endpoint instance mapping file at {mappingFilePath}. See the inner exception for more details.")); + Assert.That(exception.InnerException.InnerException.InnerException, Is.TypeOf()); + } + + static string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, nameof(When_starting_with_invalid_instance_mapping_file) + ".xml"); + + public class SenderWithInvalidMappingFile : EndpointConfigurationBuilder + { + public SenderWithInvalidMappingFile() + { + EndpointSetup(c => + { + var routingSettings = c.UseTransport().Routing(); + routingSettings.RouteToEndpoint(typeof(Message), "someReceiver"); + routingSettings.InstanceMappingFile().FilePath(mappingFilePath); + }); + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_missing_instance_mapping_file.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_missing_instance_mapping_file.cs new file mode 100644 index 00000000000..3678e150381 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_starting_with_missing_instance_mapping_file.cs @@ -0,0 +1,39 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.IO; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_starting_with_missing_instance_mapping_file : NServiceBusAcceptanceTest + { + [Test] + public void Should_throw_at_startup() + { + var exception = Assert.ThrowsAsync(() => Scenario.Define() + .WithEndpoint() + .Done(c => c.EndpointsStarted) + .Run()); + + Assert.That(exception.InnerException.InnerException.Message, Does.Contain($"The specified instance mapping file '{mappingFilePath}' does not exist.")); + } + + static string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, nameof(When_starting_with_missing_instance_mapping_file) + ".xml"); + + public class SenderWithMissingMappingFile : EndpointConfigurationBuilder + { + public SenderWithMissingMappingFile() + { + EndpointSetup(c => + { + var routingSettings = c.UseTransport().Routing(); + routingSettings.InstanceMappingFile().FilePath(mappingFilePath); + }); + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_from_a_worker.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_from_a_worker.cs new file mode 100644 index 00000000000..a0e570d0e7c --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_from_a_worker.cs @@ -0,0 +1,91 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Features; + using NServiceBus.Routing.Legacy; + using NUnit.Framework; + + public class When_subscribing_from_a_worker : NServiceBusAcceptanceTest + { + [Test] + public async Task Event_should_be_delivered_to_the_distributor() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscribed, (session, c) => session.Publish(new MyEvent()))) + .WithEndpoint() + .WithEndpoint() + .Done(c => c.DeliveredToDistributor || c.DeliveredToWorker) + .Run(); + + Assert.IsTrue(context.DeliveredToDistributor); + Assert.IsFalse(context.DeliveredToWorker); + } + + public class Context : ScenarioContext + { + public bool Subscribed { get; set; } + public bool DeliveredToDistributor { get; set; } + public bool DeliveredToWorker { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.DisableFeature(); + b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; }); + }); + } + } + + static string DistributorEndpoint => Conventions.EndpointNamingConvention(typeof(Distributor)); + + public class Distributor : EndpointConfigurationBuilder + { + public Distributor() + { + EndpointSetup(c => c.DisableFeature()); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.DeliveredToDistributor = true; + return Task.FromResult(0); + } + } + } + + public class Worker : EndpointConfigurationBuilder + { + public Worker() + { + EndpointSetup(c => { c.EnlistWithLegacyMSMQDistributor(DistributorEndpoint, DistributorEndpoint, 1); }) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.DeliveredToWorker = true; + + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_with_address_containing_host_name.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_with_address_containing_host_name.cs new file mode 100644 index 00000000000..acaf0c09b22 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_subscribing_with_address_containing_host_name.cs @@ -0,0 +1,78 @@ +namespace NServiceBus.AcceptanceTests.Routing +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Config; + using Features; + using NUnit.Framework; + using Support; + + public class When_subscribing_with_address_containing_host_name : NServiceBusAcceptanceTest + { + [Test] + public async Task Event_should_be_delivered() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscribed, (session, c) => session.Publish(new MyEvent()))) + .WithEndpoint() + .Done(c => c.Delivered) + .Run(); + + Assert.IsTrue(context.Delivered); + } + + public class Context : ScenarioContext + { + public bool Subscribed { get; set; } + public bool Delivered { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.DisableFeature(); + b.OnEndpointSubscribed((s, context) => { context.Subscribed = true; }); + }); + } + } + + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup() + .WithConfig(c => + { + c.MessageEndpointMappings = new MessageEndpointMappingCollection(); + c.MessageEndpointMappings.Add(new MessageEndpointMapping + { + Endpoint = $"{PublisherEndpoint}@{RuntimeEnvironment.MachineName}", + AssemblyName = typeof(Publisher).Assembly.GetName().Name, + TypeFullName = typeof(MyEvent).FullName + }); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent messageThatIsEnlisted, IMessageHandlerContext context) + { + Context.Delivered = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_timetobereceived_set_and_dtc.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_timetobereceived_set_and_dtc.cs new file mode 100644 index 00000000000..bbd316496c4 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_timetobereceived_set_and_dtc.cs @@ -0,0 +1,58 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_TimeToBeReceived_set_and_dtc : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_throw_on_send() + { + var context = await Scenario.Define() + .WithEndpoint(b => b.When(async (session, c) => + { + try + { + using (new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled)) + { + await session.SendLocal(new MyMessage()); + } + } + catch (Exception ex) + { + c.Exception = ex; + c.GotTheException = true; + } + })) + .Done(c => c.GotTheException) + .Run(); + + Assert.IsTrue(context.Exception.Message.EndsWith("Sending messages with a custom TimeToBeReceived is not supported on transactional MSMQ.")); + } + + public class Context : ScenarioContext + { + public bool GotTheException { get; set; } + public Exception Exception { get; set; } + } + public class TransactionalEndpoint : EndpointConfigurationBuilder + { + public TransactionalEndpoint() + { + EndpointSetup((config, context) => + { + config.UseTransport() + .Transactions(TransportTransactionMode.TransactionScope); + }); + } + } + + [TimeToBeReceived("00:00:10")] + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_unsubscribing_with_authorizer.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_unsubscribing_with_authorizer.cs new file mode 100644 index 00000000000..ee8d8dbfe57 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_unsubscribing_with_authorizer.cs @@ -0,0 +1,100 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using Features; + using Pipeline; + using NUnit.Framework; + + public class When_unsubscribing_with_authorizer : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_ignore_unsubscribe() + { + await Scenario.Define() + .WithEndpoint(b => + b.When(c => c.Subscribed, (session, c) => session.Publish(new MyEvent())) + ) + .WithEndpoint(b => b.When(async (session, context) => + { + await session.Subscribe(); + await session.Unsubscribe(); + })) + .Done(c => + c.SubscriberGotTheEvent && + c.DeclinedUnSubscribe) + .Run(TimeSpan.FromSeconds(10)); + } + + public class TestContext : ScenarioContext + { + public bool SubscriberGotTheEvent { get; set; } + public bool UnsubscribeAttempted { get; set; } + public bool DeclinedUnSubscribe { get; set; } + public bool Subscribed { get; set; } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + b.UseTransport().SubscriptionAuthorizer(Authorizer); + b.OnEndpointSubscribed((s, context) => + { + context.Subscribed = true; + if (s.SubscriberReturnAddress.Contains("Subscriber")) + { + context.UnsubscribeAttempted = true; + } + }); + b.DisableFeature(); + }); + } + + bool Authorizer(IIncomingPhysicalMessageContext context) + { + var isUnsubscribe = context + .MessageHeaders["NServiceBus.MessageIntent"] == "Unsubscribe"; + if (!isUnsubscribe) + { + return true; + } + var testContext = (TestContext)ScenarioContext; + testContext.DeclinedUnSubscribe = true; + return false; + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => c.DisableFeature()) + .AddMapping(typeof(Publisher)); + } + + public class MyEventHandler : IHandleMessages + { + TestContext context; + + public MyEventHandler(TestContext context) + { + this.context = context; + } + + public Task Handle(MyEvent message, IMessageHandlerContext handlerContext) + { + context.SubscriberGotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_using_empty_instance_mapping_file.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_using_empty_instance_mapping_file.cs new file mode 100644 index 00000000000..98a8777c211 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_using_empty_instance_mapping_file.cs @@ -0,0 +1,112 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using NUnit.Framework; + using Settings; + + public class When_using_empty_instance_mapping_file : NServiceBusAcceptanceTest + { + [SetUp] + public void SetupMappingFile() + { + // this can't be static because the conventions are setup in the NServiceBusAcceptanceTest base class + logicalEndpointName = Conventions.EndpointNamingConvention(typeof(ScaledOutReceiver)); + + // e.g. spelling error in endpoint: + File.WriteAllText(mappingFilePath, ""); + } + + [TearDown] + public void DeleteMappingFile() + { + File.Delete(mappingFilePath); + } + + [Test] + public async Task Should_send_messages_to_logical_endpoint_address() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(async c => + { + for (var i = 0; i < 10; i++) + { + await c.Send(new Message()); + } + })) + .WithEndpoint(e => e.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(e => e.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .Done(c => c.MessagesForInstance1 + c.MessagesForInstance2 >= 10) + .Run(); + + // it should send messages to the shared queue + Assert.That(context.MessagesForInstance1, Is.GreaterThanOrEqualTo(1)); + Assert.That(context.MessagesForInstance2, Is.GreaterThanOrEqualTo(1)); + } + + static string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, nameof(When_using_empty_instance_mapping_file) + ".xml"); + static string logicalEndpointName; + + public class Context : ScenarioContext + { + public int MessagesForInstance1; + public int MessagesForInstance2; + } + + public class SenderWithEmptyMappingFile : EndpointConfigurationBuilder + { + public SenderWithEmptyMappingFile() + { + EndpointSetup(c => + { + var routingSettings = c.UseTransport().Routing(); + routingSettings.RouteToEndpoint(typeof(Message), logicalEndpointName); + routingSettings.InstanceMappingFile().FilePath(mappingFilePath); + }); + } + } + + public class ScaledOutReceiver : EndpointConfigurationBuilder + { + public ScaledOutReceiver() + { + EndpointSetup(); + } + + public class MessageHandler : IHandleMessages + { + public MessageHandler(Context testContext, ReadOnlySettings settings) + { + this.testContext = testContext; + this.settings = settings; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + var instanceDiscriminator = settings.Get("EndpointInstanceDiscriminator"); + if (instanceDiscriminator == "1") + { + Interlocked.Increment(ref testContext.MessagesForInstance1); + } + if (instanceDiscriminator == "2") + { + Interlocked.Increment(ref testContext.MessagesForInstance2); + } + + return Task.FromResult(0); + } + + Context testContext; + ReadOnlySettings settings; + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_using_instance_mapping_file.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_using_instance_mapping_file.cs new file mode 100644 index 00000000000..871a36c281d --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_using_instance_mapping_file.cs @@ -0,0 +1,116 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using NUnit.Framework; + using Settings; + + public class When_using_instance_mapping_file : NServiceBusAcceptanceTest + { + [SetUp] + public void SetupMappingFile() + { + // this can't be static because the conventions are setup in the NServiceBusAcceptanceTest base class + destination = Conventions.EndpointNamingConvention(typeof(ScaledOutReceiver)); + + // e.g. spelling error in endpoint: + File.WriteAllText(mappingFilePath, + $@" + + + +"); + } + + [TearDown] + public void DeleteMappingFile() + { + File.Delete(mappingFilePath); + } + + [Test] + public async Task Should_send_messages_to_configured_instances() + { + var context = await Scenario.Define() + .WithEndpoint(e => e.When(async c => + { + for (var i = 0; i < 5; i++) + { + await c.Send(new Message()); + } + })) + .WithEndpoint(e => e.CustomConfig(c => c.MakeInstanceUniquelyAddressable("1"))) + .WithEndpoint(e => e.CustomConfig(c => c.MakeInstanceUniquelyAddressable("2"))) + .Done(c => c.MessagesForInstance1 + c.MessagesForInstance2 >= 5) + .Run(); + + Assert.That(context.MessagesForInstance1, Is.EqualTo(0)); + Assert.That(context.MessagesForInstance2, Is.EqualTo(5)); + } + + static string mappingFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, nameof(When_starting_with_invalid_instance_mapping_file) + ".xml"); + static string destination; + + public class Context : ScenarioContext + { + public int MessagesForInstance1; + public int MessagesForInstance2; + } + + public class SenderWithMappingFile : EndpointConfigurationBuilder + { + public SenderWithMappingFile() + { + EndpointSetup(c => + { + var routingSettings = c.UseTransport().Routing(); + routingSettings.RouteToEndpoint(typeof(Message), destination); + routingSettings.InstanceMappingFile().FilePath(mappingFilePath); + }); + } + } + + public class ScaledOutReceiver : EndpointConfigurationBuilder + { + public ScaledOutReceiver() + { + EndpointSetup(); + } + + public class MessageHandler : IHandleMessages + { + public MessageHandler(Context testContext, ReadOnlySettings settings) + { + this.testContext = testContext; + this.settings = settings; + } + + public Task Handle(Message message, IMessageHandlerContext context) + { + var instanceDiscriminator = settings.Get("EndpointInstanceDiscriminator"); + if (instanceDiscriminator == "1") + { + Interlocked.Increment(ref testContext.MessagesForInstance1); + } + if (instanceDiscriminator == "2") + { + Interlocked.Increment(ref testContext.MessagesForInstance2); + } + + return Task.FromResult(0); + } + + Context testContext; + ReadOnlySettings settings; + } + } + + public class Message : ICommand + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/When_using_scope_timeout_greater_than_machine_max.cs b/src/NServiceBus.Msmq.AcceptanceTests/When_using_scope_timeout_greater_than_machine_max.cs new file mode 100644 index 00000000000..0e6f9f0d8d1 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/When_using_scope_timeout_greater_than_machine_max.cs @@ -0,0 +1,35 @@ +namespace NServiceBus.AcceptanceTests +{ + using System; + using AcceptanceTesting; + using NUnit.Framework; + + public class When_using_scope_timeout_greater_than_machine_max : NServiceBusAcceptanceTest + { + [Test] + public void Should_blow_up() + { + var aex = Assert.ThrowsAsync(async () => + { + await Scenario.Define() + .WithEndpoint() + .Run(); + }); + + Assert.True(aex.InnerException.InnerException.Message.Contains("Timeout requested is longer than the maximum value for this machine")); + } + + public class ScopeEndpoint : EndpointConfigurationBuilder + { + public ScopeEndpoint() + { + EndpointSetup(c => + { + c.UseTransport() + .Transactions(TransportTransactionMode.TransactionScope) + .TransactionScopeOptions(timeout: TimeSpan.FromHours(1)); + }); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Msmq.AcceptanceTests/packages.config b/src/NServiceBus.Msmq.AcceptanceTests/packages.config new file mode 100644 index 00000000000..ed3215e9854 --- /dev/null +++ b/src/NServiceBus.Msmq.AcceptanceTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Encryption/ClassForNesting.cs b/src/NServiceBus.PerformanceTests/Encryption/ClassForNesting.cs deleted file mode 100644 index c9047edb5f9..00000000000 --- a/src/NServiceBus.PerformanceTests/Encryption/ClassForNesting.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Runner.Encryption -{ - using NServiceBus; - - public class ClassForNesting - { - public WireEncryptedString EncryptedProperty { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessage.cs b/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessage.cs deleted file mode 100644 index 3f9d99fcf3f..00000000000 --- a/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using NServiceBus; - -// ReSharper disable UnusedAutoPropertyAccessor.Global -namespace Runner.Encryption -{ - public class EncryptionTestMessage : MessageBase - { - public WireEncryptedString Secret { get; set; } - public ClassForNesting CreditCard { get; set; } - public WireEncryptedString SecretThatIsNull { get; set; } - public DateTime DateTime { get; set; } - public List ListOfCreditCards { get; set; } - public ArrayList ListOfSecrets { get; set; } - public byte[] LargeByteArray { get; set; } - } -} - -// ReSharper restore UnusedAutoPropertyAccessor.Global \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessageHandler.cs b/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessageHandler.cs deleted file mode 100644 index 26aa021c0e6..00000000000 --- a/src/NServiceBus.PerformanceTests/Encryption/EncryptionTestMessageHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Runner.Encryption -{ - using System; - using System.Threading; - using System.Transactions; - - using NServiceBus; - - class TestMessageHandler : IHandleMessages - { - private static TwoPhaseCommitEnlistment enlistment = new TwoPhaseCommitEnlistment(); - - public void Handle(EncryptionTestMessage message) - { - if (!Statistics.First.HasValue) - { - Statistics.First = DateTime.Now; - } - Interlocked.Increment(ref Statistics.NumberOfMessages); - - if (message.TwoPhaseCommit) - { - Transaction.Current.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - } - - Statistics.Last = DateTime.Now; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/MessageBase.cs b/src/NServiceBus.PerformanceTests/MessageBase.cs deleted file mode 100644 index bfe087c8f2e..00000000000 --- a/src/NServiceBus.PerformanceTests/MessageBase.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Runner -{ - using System; - - using NServiceBus; - - [Serializable] - public class MessageBase : IMessage - { - public int Id { get; set; } - public bool TwoPhaseCommit { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/MessageForwardingInCaseOfFaultConfigOverride.cs b/src/NServiceBus.PerformanceTests/MessageForwardingInCaseOfFaultConfigOverride.cs deleted file mode 100644 index 16ae90f217d..00000000000 --- a/src/NServiceBus.PerformanceTests/MessageForwardingInCaseOfFaultConfigOverride.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Runner -{ - using NServiceBus.Config; - using NServiceBus.Config.ConfigurationSource; - - class MessageForwardingInCaseOfFaultConfigOverride : IProvideConfiguration - { - public MessageForwardingInCaseOfFaultConfig GetConfiguration() - { - return new MessageForwardingInCaseOfFaultConfig - { - ErrorQueue = "error" - }; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/NServiceBus.PerformanceTests.csproj b/src/NServiceBus.PerformanceTests/NServiceBus.PerformanceTests.csproj deleted file mode 100644 index a721eedc1f6..00000000000 --- a/src/NServiceBus.PerformanceTests/NServiceBus.PerformanceTests.csproj +++ /dev/null @@ -1,100 +0,0 @@ - - - - Debug - AnyCPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D} - Exe - Properties - NServiceBus.PerformanceTests - NServiceBus.PerformanceTests - v4.5 - 512 - true - ..\Test.snk - ..\ - - False - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - true - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {DD48B2D0-E996-412D-9157-821ED8B17A9D} - NServiceBus.Core - - - - \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Program.cs b/src/NServiceBus.PerformanceTests/Program.cs deleted file mode 100644 index 784badda2ec..00000000000 --- a/src/NServiceBus.PerformanceTests/Program.cs +++ /dev/null @@ -1,220 +0,0 @@ -namespace Runner -{ - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Threading; - using System.Threading.Tasks; - using System.Transactions; - using NServiceBus; - using NServiceBus.Features; - using Encryption; - using Saga; - using System; - using System.Text; - using MsmqTransport = NServiceBus.MsmqTransport; - - class Program - { - static void Main(string[] args) - { - var testCaseToRun = args[0]; - - int numberOfThreads; - - if (!int.TryParse(testCaseToRun, out numberOfThreads)) - { - var testCase = TestCase.Load(testCaseToRun,args); - - testCase.Run(); - testCase.DumpSettings(); - - return; - } - - var volatileMode = (args[4].ToLower() == "volatile"); - var suppressDTC = (args[4].ToLower() == "suppressdtc"); - var twoPhaseCommit = (args[4].ToLower() == "twophasecommit"); - var saga = (args[5].ToLower() == "sagamessages"); - var encryption = (args[5].ToLower() == "encryption"); - var concurrency = int.Parse(args[7]); - - TransportConfigOverride.MaximumConcurrencyLevel = numberOfThreads; - - var numberOfMessages = int.Parse(args[1]); - - var endpointName = "PerformanceTest"; - - if (volatileMode) - endpointName += ".Volatile"; - - if (suppressDTC) - endpointName += ".SuppressDTC"; - - var configuration = new BusConfiguration(); - - configuration.EndpointName(endpointName); - configuration.EnableInstallers(); - configuration.DiscardFailedMessagesInsteadOfSendingToErrorQueue(); - configuration.UseTransport().ConnectionString("deadLetter=false;journal=false"); - configuration.DisableFeature(); - - if (volatileMode) - { - configuration.DisableDurableMessages(); - configuration.UsePersistence(); - } - - switch (args[3].ToLower()) - { - case "msmq": - configuration.UseTransport(); - break; - - default: - throw new InvalidOperationException("Illegal transport " + args[2]); - } - - if (suppressDTC) - { - configuration.Transactions().DisableDistributedTransactions(); - } - - switch (args[2].ToLower()) - { - case "xml": - configuration.UseSerialization(); - break; - - case "json": - configuration.UseSerialization(); - break; - - case "bson": - configuration.UseSerialization(); - break; - - case "bin": - configuration.UseSerialization(); - break; - - default: - throw new InvalidOperationException("Illegal serialization format " + args[2]); - } - configuration.UsePersistence(); - configuration.RijndaelEncryptionService("KEY", Encoding.ASCII.GetBytes("gdDbqRpqdRbTs3mhdZh9qCaDaxJXl+e6")); - - - using (var startableBus = Bus.Create(configuration)) - { - if (saga) - { - SeedSagaMessages(startableBus,numberOfMessages, endpointName, concurrency); - } - else - { - Statistics.SendTimeNoTx = SeedInputQueue(startableBus,numberOfMessages / 2, endpointName, numberOfThreads, false, twoPhaseCommit, encryption); - Statistics.SendTimeWithTx = SeedInputQueue(startableBus,numberOfMessages / 2, endpointName, numberOfThreads, true, twoPhaseCommit, encryption); - } - - Statistics.StartTime = DateTime.Now; - - startableBus.Start(); - - while (Interlocked.Read(ref Statistics.NumberOfMessages) < numberOfMessages) - Thread.Sleep(1000); - - DumpSetting(args); - Statistics.Dump(); - } - } - - static void DumpSetting(string[] args) - { - Console.Out.WriteLine("---------------- Settings ----------------"); - Console.Out.WriteLine("Threads: {0}, Serialization: {1}, Transport: {2}, Messagemode: {3}", - args[0], - args[2], - args[3], - args[5]); - } - - static void SeedSagaMessages(IBus bus,int numberOfMessages, string inputQueue, int concurrency) - { - for (var i = 0; i < numberOfMessages / concurrency; i++) - { - - for (var j = 0; j < concurrency; j++) - { - bus.Send(inputQueue, new StartSagaMessage - { - Id = i - }); - } - } - - } - - static TimeSpan SeedInputQueue(IBus bus,int numberOfMessages, string inputQueue, int numberOfThreads, bool createTransaction, bool twoPhaseCommit, bool encryption) - { - var sw = new Stopwatch(); - - - sw.Start(); - Parallel.For( - 0, - numberOfMessages, - new ParallelOptions { MaxDegreeOfParallelism = numberOfThreads }, - x => - { - var message = CreateMessage(encryption); - message.TwoPhaseCommit = twoPhaseCommit; - message.Id = x; - - if (createTransaction) - { - using (var tx = new TransactionScope()) - { - bus.Send(inputQueue, message); - tx.Complete(); - } - } - else - { - bus.Send(inputQueue, message); - } - }); - sw.Stop(); - - return sw.Elapsed; - } - - public const string EncryptedBase64Value = "encrypted value"; - const string MySecretMessage = "A secret"; - - static MessageBase CreateMessage(bool encryption) - { - if (encryption) - { - // need a new instance of a message each time - var message = new EncryptionTestMessage - { - Secret = MySecretMessage, - CreditCard = new ClassForNesting { EncryptedProperty = MySecretMessage }, - LargeByteArray = new byte[1], // the length of the array is not the issue now - ListOfCreditCards = - new List - { - new ClassForNesting {EncryptedProperty = MySecretMessage}, - new ClassForNesting {EncryptedProperty = MySecretMessage} - } - }; - message.ListOfSecrets = new ArrayList(message.ListOfCreditCards); - - return message; - } - - return new TestMessage(); - } - } -} diff --git a/src/NServiceBus.PerformanceTests/PubSub/PubSubTestCase.cs b/src/NServiceBus.PerformanceTests/PubSub/PubSubTestCase.cs deleted file mode 100644 index e6eddadcb0e..00000000000 --- a/src/NServiceBus.PerformanceTests/PubSub/PubSubTestCase.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Transactions; -using NServiceBus; -using NServiceBus.Features; -using NServiceBus.Transports.Msmq; -using NServiceBus.Transports.Msmq.Config; -using NServiceBus.Unicast.Subscriptions; -using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; -using PublishTestMessages; -using Runner; -using MsmqTransport = NServiceBus.MsmqTransport; - -public class PubSubTestCase : TestCase -{ - int GetNumberOfSubscribers() - { - int value; - if (!int.TryParse(GetParameterValue("numberofsubscribers"), out value)) - { - return 10; - } - return value; - } - - string GetStorageType() - { - var value = GetParameterValue("storage"); - - if (string.IsNullOrEmpty(value)) - { - return "inmemory"; - } - return value.ToLower(); - } - - - public override void Run() - { - TransportConfigOverride.MaximumConcurrencyLevel = NumberOfThreads; - - var configuration = new BusConfiguration(); - - configuration.EndpointName("PubSubPerformanceTest"); - configuration.EnableInstallers(); - configuration.DiscardFailedMessagesInsteadOfSendingToErrorQueue(); - configuration.UseTransport(); - configuration.DisableFeature(); - - switch (GetStorageType()) - { - case "inmemory": - configuration.UsePersistence(); - break; - case "msmq": - configuration.UsePersistence(); - break; - } - - - using (var bus = Bus.Create(configuration)) - { - var subscriptionStorage = ((NServiceBus.Unicast.UnicastBus) bus).Builder.Build(); - - - PrimeSubscriptionStorage(subscriptionStorage); - - Parallel.For( - 0, - NumberMessages, - new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, - x => bus.SendLocal(new PerformPublish())); - - - Statistics.StartTime = DateTime.Now; - bus.Start(); - - while (Interlocked.Read(ref Statistics.NumberOfMessages) < NumberMessages) - Thread.Sleep(1000); - - - Statistics.Dump(); - } - - } - - void PrimeSubscriptionStorage(ISubscriptionStorage subscriptionStorage) - { - var testEventMessage = new MessageType(typeof(TestEvent)); - - - subscriptionStorage.Init(); - - - var creator = new MsmqQueueCreator - { - Settings = new MsmqSettings - { - UseTransactionalQueues = true - } - }; - - for (var i = 0; i < GetNumberOfSubscribers(); i++) - { - var subscriberAddress = Address.Parse("PubSubPerformanceTest.Subscriber" + i); - creator.CreateQueueIfNecessary(subscriberAddress, null); - - using (var tx = new TransactionScope()) - { - subscriptionStorage.Subscribe(subscriberAddress, new List - { - testEventMessage - }); - - tx.Complete(); - } - } - } -} - -class PublishEventHandler : IHandleMessages -{ - public IBus Bus { get; set; } - public void Handle(PerformPublish message) - { - Bus.Publish(); - } -} - -namespace PublishTestMessages -{ - public class PerformPublish : IMessage - { - } - - public class TestEvent : IEvent - { - } -} diff --git a/src/NServiceBus.PerformanceTests/PubSub/TestCase.cs b/src/NServiceBus.PerformanceTests/PubSub/TestCase.cs deleted file mode 100644 index 91fc5532027..00000000000 --- a/src/NServiceBus.PerformanceTests/PubSub/TestCase.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -public abstract class TestCase -{ - - protected int NumberOfThreads - { - get - { - int value; - - if (!int.TryParse(GetParameterValue("numberofthreads"), out value)) - { - return 10; - } - - return value; - } - } - - protected int NumberMessages - { - get - { - int value; - - if (!int.TryParse(GetParameterValue("numberofmessages"), out value)) - { - return 10000; - } - - return value; - } - } - - protected string GetParameterValue(string key) - { - string value; - - if (!parameters.TryGetValue(key, out value)) - { - return null; - } - - return value; - - } - - - protected Dictionary parameters = new Dictionary(); - - void WithParameters(Dictionary testCaseParameters) - { - parameters = testCaseParameters; - } - - - public abstract void Run(); - - public static TestCase Load(string testCaseToRun, IEnumerable args) - { - var typeName = testCaseToRun; - - if (!testCaseToRun.Contains("TestCase")) - { - typeName += "TestCase"; - } - var testCase = (TestCase)Activator.CreateInstance(Type.GetType(typeName)); - - var parameters = args.Where(arg => arg.Contains("=")).Select(arg => new KeyValuePair(arg.Split('=').First().ToLower(), arg.Split('=').Last())) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - testCase.WithParameters(parameters); - - return testCase; - } - - public void DumpSettings() - { - var settings = GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Select(pi => new - { - Name = pi.Name, - Value = pi.GetValue(this, null) - }).ToList(); - - Console.Out.WriteLine("Settings: {0}",string.Join(" ",settings.Select(s=>string.Format("{0}={1}",s.Name,s.Value)))); - } - -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Saga/CompleteSagaMessage.cs b/src/NServiceBus.PerformanceTests/Saga/CompleteSagaMessage.cs deleted file mode 100644 index 054532fe1cb..00000000000 --- a/src/NServiceBus.PerformanceTests/Saga/CompleteSagaMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Runner.Saga -{ - using System; - - [Serializable] - public class CompleteSagaMessage : MessageBase - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Saga/SagaData.cs b/src/NServiceBus.PerformanceTests/Saga/SagaData.cs deleted file mode 100644 index 3967ab1ecd7..00000000000 --- a/src/NServiceBus.PerformanceTests/Saga/SagaData.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Runner.Saga -{ - using System; - using NServiceBus.Saga; - - public class SagaData : IContainSagaData - { - public virtual string Originator { get; set; } - - public virtual string OriginalMessageId { get; set; } - - public virtual Guid Id { get; set; } - - [Unique] - public virtual int Number { get; set; } - - public virtual int NumCalls { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Saga/StartSagaMessage.cs b/src/NServiceBus.PerformanceTests/Saga/StartSagaMessage.cs deleted file mode 100644 index f6c717e9ac3..00000000000 --- a/src/NServiceBus.PerformanceTests/Saga/StartSagaMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Runner.Saga -{ - using System; - - [Serializable] - public class StartSagaMessage : MessageBase - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Saga/TestSaga.cs b/src/NServiceBus.PerformanceTests/Saga/TestSaga.cs deleted file mode 100644 index 305a6703c0d..00000000000 --- a/src/NServiceBus.PerformanceTests/Saga/TestSaga.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Runner.Saga -{ - using NServiceBus; - using NServiceBus.Saga; - - class TestSaga : Saga, IAmStartedByMessages, IHandleMessages - { - public void Handle(StartSagaMessage message) - { - Data.Number = message.Id; - Data.NumCalls++; - } - - public void Handle(CompleteSagaMessage message) - { - MarkAsComplete(); - } - - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) - { - mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.Number); - mapper.ConfigureMapping(m => m.Id).ToSaga(s => s.Number); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/Statistics.cs b/src/NServiceBus.PerformanceTests/Statistics.cs deleted file mode 100644 index 7358afc5d0c..00000000000 --- a/src/NServiceBus.PerformanceTests/Statistics.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Runner -{ - using System; - - class Statistics - { - public static DateTime? First; - public static DateTime Last; - public static DateTime StartTime; - public static Int64 NumberOfMessages; - public static Int64 NumberOfRetries; - public static TimeSpan SendTimeNoTx = TimeSpan.Zero; - public static TimeSpan SendTimeWithTx = TimeSpan.Zero; - - public static void Dump() - { - Console.Out.WriteLine(""); - Console.Out.WriteLine("---------------- Statistics ----------------"); - - var durationSeconds = (Last - First.Value).TotalSeconds; - - PrintStats("NumberOfMessages", NumberOfMessages, "#"); - - var throughput = Convert.ToDouble(NumberOfMessages)/durationSeconds; - - PrintStats("Throughput", throughput, "msg/s"); - - Console.Out.WriteLine("##teamcity[buildStatisticValue key='ReceiveThroughput' value='{0}']", Math.Round(throughput)); - - PrintStats("NumberOfRetries", NumberOfRetries, "#"); - PrintStats("TimeToFirstMessage", (First - StartTime).Value.TotalSeconds, "s"); - - if (SendTimeNoTx != TimeSpan.Zero) - PrintStats("Sending", Convert.ToDouble(NumberOfMessages / 2) / SendTimeNoTx.TotalSeconds, "msg/s"); - - if (SendTimeWithTx != TimeSpan.Zero) - PrintStats("SendingInsideTX", Convert.ToDouble(NumberOfMessages / 2) / SendTimeWithTx.TotalSeconds, "msg/s"); - } - - static void PrintStats(string key, double value, string unit) - { - Console.Out.WriteLine("{0}: {1:0.0} ({2})", key, value, unit); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/StatisticsUoW.cs b/src/NServiceBus.PerformanceTests/StatisticsUoW.cs deleted file mode 100644 index c57667e3cd7..00000000000 --- a/src/NServiceBus.PerformanceTests/StatisticsUoW.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace Runner -{ - using System; - using System.Threading; - using System.Transactions; - using NServiceBus; - using NServiceBus.UnitOfWork; - - class StatisticsUoW : IManageUnitsOfWork, INeedInitialization - { - public void Begin() - { - if (!Statistics.First.HasValue) - { - Statistics.First = DateTime.Now; - } - - if(Transaction.Current != null) - Transaction.Current.TransactionCompleted += OnCompleted; - - } - - void OnCompleted(object sender, TransactionEventArgs e) - { - if (e.Transaction.TransactionInformation.Status != TransactionStatus.Committed) - { - return; - } - - RecordSuccess(); - } - - static void RecordSuccess() - { - Statistics.Last = DateTime.Now; - Interlocked.Increment(ref Statistics.NumberOfMessages); - } - - public void End(Exception ex = null) - { - if (ex != null) - { - Interlocked.Increment(ref Statistics.NumberOfRetries); - return; - } - - if(Transaction.Current == null) - RecordSuccess(); - } - - public void Customize(BusConfiguration configuration) - { - configuration.RegisterComponents(c => c.ConfigureComponent(DependencyLifecycle.InstancePerUnitOfWork)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestMessage.cs b/src/NServiceBus.PerformanceTests/TestMessage.cs deleted file mode 100644 index 7908572f556..00000000000 --- a/src/NServiceBus.PerformanceTests/TestMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Runner -{ - using System; - - [Serializable] - public class TestMessage : MessageBase - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestMessageHandler.cs b/src/NServiceBus.PerformanceTests/TestMessageHandler.cs deleted file mode 100644 index c46d9919864..00000000000 --- a/src/NServiceBus.PerformanceTests/TestMessageHandler.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Runner -{ - using System; - using System.Threading; - using System.Transactions; - - using NServiceBus; - - class TestMessageHandler:IHandleMessages - { - private static TwoPhaseCommitEnlistment enlistment = new TwoPhaseCommitEnlistment(); - - public void Handle(TestMessage message) - { - if (!Statistics.First.HasValue) - { - Statistics.First = DateTime.Now; - } - Interlocked.Increment(ref Statistics.NumberOfMessages); - - if (message.TwoPhaseCommit) - { - Transaction.Current.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); - } - - Statistics.Last = DateTime.Now; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestObject.cs b/src/NServiceBus.PerformanceTests/TestObject.cs deleted file mode 100644 index 2240c09d03f..00000000000 --- a/src/NServiceBus.PerformanceTests/TestObject.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Runner -{ - using System; - - public class TestObject - { - public Guid Id { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Mutators.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Mutators.ps1 deleted file mode 100644 index 6487ba5a2f8..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Mutators.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -. .\TestSupport.ps1 - -#runs messages using a message with encrypted properties - -RunTest -serializationFormat "json" -transport "msmq" -messagemode "encryption" -numMessages 1000 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/PubSub.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/PubSub.ps1 deleted file mode 100644 index 49fe535a193..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/PubSub.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -. .\TestSupport.ps1 - -Cleanup - -..\.\bin\release\Runner.exe PubSub numberofsubscribers=10 numberofmessages=10000 numberofthreads=10 storage=msmq - -Cleanup - - -..\.\bin\release\Runner.exe PubSub numberofsubscribers=10 numberofmessages=10000 numberofthreads=10 storage=inmemory - -Cleanup - -..\.\bin\release\Runner.exe PubSub numberofsubscribers=10 numberofmessages=10000 numberofthreads=10 storage=inmemory - -Cleanup diff --git a/src/NServiceBus.PerformanceTests/TestSuites/RabbitMQ.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/RabbitMQ.ps1 deleted file mode 100644 index 3b466a3ef9d..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/RabbitMQ.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -numThreads 60 -transport "rabbitmq" -RunTest -numThreads 60 -transport "rabbitmq" -mode "volatile" - -RunTest -numThreads 1 -transport "rabbitmq" -RunTest -numThreads 5 -transport "rabbitmq" -RunTest -numThreads 10 -transport "rabbitmq" -RunTest -numThreads 15 -transport "rabbitmq" -RunTest -numThreads 60 -transport "rabbitmq" -RunTest -numThreads 90 -transport "rabbitmq" - diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Receive-Durable.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Receive-Durable.ps1 deleted file mode 100644 index 1e51ac0ac1b..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Receive-Durable.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -. .\TestSupport.ps1 - -RunTest diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Reset-Database.sql b/src/NServiceBus.PerformanceTests/TestSuites/Reset-Database.sql deleted file mode 100644 index dd9451aedcb..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Reset-Database.sql +++ /dev/null @@ -1,101 +0,0 @@ -/* Drop all non-system stored procs */ -DECLARE @name VARCHAR(128) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'P' AND category = 0 ORDER BY [name]) - -WHILE @name is not null -BEGIN - SELECT @SQL = 'DROP PROCEDURE [dbo].[' + RTRIM(@name) +']' - EXEC (@SQL) - PRINT 'Dropped Procedure: ' + @name - SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'P' AND category = 0 AND [name] > @name ORDER BY [name]) -END -GO - -/* Drop all views */ -DECLARE @name VARCHAR(128) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'V' AND category = 0 ORDER BY [name]) - -WHILE @name IS NOT NULL -BEGIN - SELECT @SQL = 'DROP VIEW [dbo].[' + RTRIM(@name) +']' - EXEC (@SQL) - PRINT 'Dropped View: ' + @name - SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'V' AND category = 0 AND [name] > @name ORDER BY [name]) -END -GO - -/* Drop all functions */ -DECLARE @name VARCHAR(128) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] IN (N'FN', N'IF', N'TF', N'FS', N'FT') AND category = 0 ORDER BY [name]) - -WHILE @name IS NOT NULL -BEGIN - SELECT @SQL = 'DROP FUNCTION [dbo].[' + RTRIM(@name) +']' - EXEC (@SQL) - PRINT 'Dropped Function: ' + @name - SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] IN (N'FN', N'IF', N'TF', N'FS', N'FT') AND category = 0 AND [name] > @name ORDER BY [name]) -END -GO - -/* Drop all Foreign Key constraints */ -DECLARE @name VARCHAR(128) -DECLARE @constraint VARCHAR(254) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY TABLE_NAME) - -WHILE @name is not null -BEGIN - SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME) - WHILE @constraint IS NOT NULL - BEGIN - SELECT @SQL = 'ALTER TABLE [dbo].[' + RTRIM(@name) +'] DROP CONSTRAINT [' + RTRIM(@constraint) +']' - EXEC (@SQL) - PRINT 'Dropped FK Constraint: ' + @constraint + ' on ' + @name - SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' AND CONSTRAINT_NAME <> @constraint AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME) - END -SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY TABLE_NAME) -END -GO - -/* Drop all Primary Key constraints */ -DECLARE @name VARCHAR(128) -DECLARE @constraint VARCHAR(254) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' ORDER BY TABLE_NAME) - -WHILE @name IS NOT NULL -BEGIN - SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME) - WHILE @constraint is not null - BEGIN - SELECT @SQL = 'ALTER TABLE [dbo].[' + RTRIM(@name) +'] DROP CONSTRAINT [' + RTRIM(@constraint)+']' - EXEC (@SQL) - PRINT 'Dropped PK Constraint: ' + @constraint + ' on ' + @name - SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' AND CONSTRAINT_NAME <> @constraint AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME) - END -SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' ORDER BY TABLE_NAME) -END -GO - -/* Drop all tables */ -DECLARE @name VARCHAR(128) -DECLARE @SQL VARCHAR(254) - -SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'U' AND category = 0 ORDER BY [name]) - -WHILE @name IS NOT NULL -BEGIN - SELECT @SQL = 'DROP TABLE [dbo].[' + RTRIM(@name) +']' - EXEC (@SQL) - PRINT 'Dropped Table: ' + @name - SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'U' AND category = 0 AND [name] > @name ORDER BY [name]) -END -GO \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/RunAllSuites.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/RunAllSuites.ps1 deleted file mode 100644 index 77aa2e6b6aa..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/RunAllSuites.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -"Threads" -.\Threads.ps1 - -"Serializers" -.\Serializers.ps1 - -"Transports" -.\Transports.ps1 - -"Mutators" -.\Mutators.ps1 \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Sagas-Concurrency.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Sagas-Concurrency.ps1 deleted file mode 100644 index 6816c54725a..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Sagas-Concurrency.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -. .\TestSupport.ps1 - -Cleanup - -"C=0, no sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 1 - -"C=0, existing sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 1 - -Cleanup - -"C=2, no sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 2 - -"C=2, existing sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 2 - -Cleanup - -"C=5, no sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 5 - -"C=5, existing sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 5 - -Cleanup - -"C=10, no sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 10 - -"C=10, existing sagas" -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 2000 -persistence nhibernate -concurrency 10 \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Serializers.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Serializers.ps1 deleted file mode 100644 index d673cb13558..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Serializers.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -serializationFormat "xml" -RunTest -serializationFormat "json" -RunTest -serializationFormat "bson" -RunTest -serializationFormat "bin" diff --git a/src/NServiceBus.PerformanceTests/TestSuites/SqlServer.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/SqlServer.ps1 deleted file mode 100644 index f83f72a62d1..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/SqlServer.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -. .\TestSupport.ps1 - -#RunTest -numThreads 15 -transport "sqlserver" -#RunTest -numThreads 15 -transport "sqlserver" -mode "volatile" - -RunTest -numThreads 1 -transport "sqlserver" -RunTest -numThreads 5 -transport "sqlserver" -RunTest -numThreads 10 -transport "sqlserver" -RunTest -numThreads 15 -transport "sqlserver" -RunTest -numThreads 60 -transport "sqlserver" -RunTest -numThreads 90 -transport "sqlserver" - diff --git a/src/NServiceBus.PerformanceTests/TestSuites/TestSupport.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/TestSupport.ps1 deleted file mode 100644 index 2948ab1131d..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/TestSupport.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -function RunTest () -{ - param( - [Parameter(Position=0,Mandatory=$false)] - [int] $numThreads = 10, - [Parameter(Position=1,Mandatory=$false)] - [int] $numMessages = 10000, - [Parameter(Position=2,Mandatory=$false)] - [string] $serializationFormat = "xml", - [Parameter(Position=3,Mandatory=$false)] - [string] $transport = "msmq", - [Parameter(Position=4,Mandatory=$false)] - [string] $mode = "nonvolatile", - [Parameter(Position=5,Mandatory=$false)] - [string] $messagemode = "normalmessages", - [Parameter(Position=6,Mandatory=$false)] - [string] $persistence = "inmemory", - [Parameter(Position=7,Mandatory=$false)] - [string] $concurrency = "1" -) - - $file = "..\.\bin\debug\Runner.exe" - $fullpath = Resolve-Path $file -ErrorAction SilentlyContinue - - if (!$fullpath) { - throw "{0} not found - exiting..." -f $file - } - else { - . $file $numThreads $numMessages $serializationFormat $transport $mode $messagemode $persistence $concurrency - } -} - -function Reset-Msmq() { - AssertIsAnAdministrator - if (-not ([System.Management.Automation.PSTypeName]'System.Messaging.MessageQueue').Type) { - [void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging") - } - - [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine("localhost") | % {".\" + $_.QueueName} | % {[System.Messaging.MessageQueue]::Delete($_) } | Out-Null -} - - -function Cleanup() -{ - sqlcmd -S .\SQLEXPRESS -d NServiceBus -i .\Reset-Database.sql | Out-Null - Reset-Msmq -} - -function AssertIsAnAdministrator() -{ - $currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $currentPrincipal = new-object System.Security.Principal.WindowsPrincipal($currentIdentity) - $adminRole = [ System.Security.Principal.WindowsBuiltInRole]::Administrator - - if (-not $currentPrincipal.IsInRole($adminRole)) { - throw "Elevation required to run" - } -} - - - - - \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Threads.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Threads.ps1 deleted file mode 100644 index 4b521a5f006..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Threads.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -numThreads 1 -RunTest -numThreads 5 -RunTest -numThreads 10 -RunTest -numThreads 15 -RunTest -numThreads 30 -RunTest -numThreads 60 -RunTest -numThreads 90 \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Sagas.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-Sagas.ps1 deleted file mode 100644 index 3d53ca58788..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Sagas.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -. .\TestSupport.ps1 - -#runs all transport in nonvolatile m - -RunTest -transport "msmq" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "activemq" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "sqlserver" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "rabbitmq" -messagemode "sagamessages" -numMessages 1000 -numThreads 60 -#RunTest -transport "azure" -messagemode "sagamessages" -numMessages 1000 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC-Sagas.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC-Sagas.ps1 deleted file mode 100644 index 6e18e8d55f1..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC-Sagas.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -transport "msmq" -mode "suppressDTC" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "sqlserver" -mode "suppressDTC" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "activemq" -mode "suppressDTC" -messagemode "sagamessages" -numMessages 1000 -RunTest -transport "rabbitmq" -mode "suppressDTC" -messagemode "sagamessages" -numMessages 1000 -numThreads 60 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC.ps1 deleted file mode 100644 index 023b7da7596..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-SuppressDTC.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -transport "msmq" -mode "suppressDTC" -RunTest -transport "sqlserver" -mode "suppressDTC" -RunTest -transport "activemq" -mode "suppressDTC" -RunTest -transport "rabbitmq" -mode "suppressDTC" -numThreads 60 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-TwoPhaseCommit.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-TwoPhaseCommit.ps1 deleted file mode 100644 index bd8977bc32b..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-TwoPhaseCommit.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -transport "msmq" -mode "twophasecommit" -RunTest -transport "sqlserver" -mode "twophasecommit" -RunTest -transport "activemq" -mode "twophasecommit" -RunTest -transport "rabbitmq" -mode "twophasecommit" -numThreads 60 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile-Sagas.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile-Sagas.ps1 deleted file mode 100644 index 1999c4d0c9f..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile-Sagas.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -transport "msmq" -mode "volatile" -messagemode "sagamessages" -numMessages 100 -RunTest -transport "sqlserver" -mode "volatile" -messagemode "sagamessages" -numMessages 100 -RunTest -transport "activemq" -mode "volatile" -messagemode "sagamessages" -numMessages 100 -RunTest -transport "rabbitmq" -mode "volatile" -messagemode "sagamessages" -numMessages 100 -numThreads 60 diff --git a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile.ps1 b/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile.ps1 deleted file mode 100644 index d4b1968b6d1..00000000000 --- a/src/NServiceBus.PerformanceTests/TestSuites/Transports-Volatile.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -. .\TestSupport.ps1 - -RunTest -transport "msmq" -mode "volatile" -RunTest -transport "sqlserver" -mode "volatile" -RunTest -transpclsort "activemq" -mode "volatile" -RunTest -transport "rabbitmq" -mode "volatile" -numThreads 60 diff --git a/src/NServiceBus.PerformanceTests/TransportConfigOverride.cs b/src/NServiceBus.PerformanceTests/TransportConfigOverride.cs deleted file mode 100644 index a1d4bbaf54e..00000000000 --- a/src/NServiceBus.PerformanceTests/TransportConfigOverride.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Runner -{ - using NServiceBus.Config; - using NServiceBus.Config.ConfigurationSource; - - class TransportConfigOverride : IProvideConfiguration - { - public static int MaximumConcurrencyLevel; - public TransportConfig GetConfiguration() - { - return new TransportConfig - { - MaximumConcurrencyLevel = MaximumConcurrencyLevel, - MaxRetries = 10, - MaximumMessageThroughputPerSecond = 0 - }; - } - } - -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/TwoPhaseCommitEnlistment.cs b/src/NServiceBus.PerformanceTests/TwoPhaseCommitEnlistment.cs deleted file mode 100644 index 8ff136af3d9..00000000000 --- a/src/NServiceBus.PerformanceTests/TwoPhaseCommitEnlistment.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Runner -{ - using System.Transactions; - - class TwoPhaseCommitEnlistment : ISinglePhaseNotification - { - public void Prepare(PreparingEnlistment preparingEnlistment) - { - preparingEnlistment.Prepared(); - } - - public void Commit(Enlistment enlistment) - { - enlistment.Done(); - } - - public void Rollback(Enlistment enlistment) - { - enlistment.Done(); - } - - public void InDoubt(Enlistment enlistment) - { - enlistment.Done(); - } - - public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) - { - singlePhaseEnlistment.Committed(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PerformanceTests/packages.config b/src/NServiceBus.PerformanceTests/packages.config deleted file mode 100644 index 6b8deb9c96d..00000000000 --- a/src/NServiceBus.PerformanceTests/packages.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddAuditConfig.cs b/src/NServiceBus.PowerShell.Development/AddAuditConfig.cs deleted file mode 100644 index 40c5d1cf5dd..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddAuditConfig.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Collections; - using System.Linq; - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusAuditConfig")] - public class AddAuditConfig : AddConfigSection - { - const string Instructions = @""; - - const string exampleAuditConfigSection = @"
"; - - public override void ModifyConfig(XDocument doc) - { - // Add the new audit config section, if the ForwardReceivedMessagesTo attribute has not been set in the UnicastBusConfig. - var frmAttributeEnumerator = (IEnumerable)doc.XPathEvaluate("/configuration/UnicastBusConfig/@ForwardReceivedMessagesTo"); - var isForwardReceivedMessagesAttributeDefined = frmAttributeEnumerator.Cast().Any(); - - // Then add the audit config - var sectionElement = - doc.XPathSelectElement( - "/configuration/configSections/section[@name='AuditConfig' and @type='NServiceBus.Config.AuditConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - if (isForwardReceivedMessagesAttributeDefined) - doc.XPathSelectElement("/configuration/configSections").Add(new XComment(exampleAuditConfigSection)); - else - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", - "AuditConfig"), - new XAttribute("type", - "NServiceBus.Config.AuditConfig, NServiceBus.Core"))); - } - - var forwardingElement = doc.XPathSelectElement("/configuration/AuditConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - isForwardReceivedMessagesAttributeDefined ? (object) new XComment(@"Since we detected that you already have forwarding setup we haven't enabled the audit feature. -Please remove the ForwardReceivedMessagesTo attribute from the UnicastBusConfig and uncomment the AuditConfig section. -") : new XElement("AuditConfig", new XAttribute("QueueName", "audit"))); - } - - } - } -} diff --git a/src/NServiceBus.PowerShell.Development/AddConfigSection.cs b/src/NServiceBus.PowerShell.Development/AddConfigSection.cs deleted file mode 100644 index 765c80a3c71..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddConfigSection.cs +++ /dev/null @@ -1,143 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Xml; - using System.Xml.Linq; - using System.Xml.XPath; - - public abstract class AddConfigSection : PSCmdlet - { - [Parameter(Position = 0, Mandatory = true, HelpMessage = "Specifies the project containing the project to update.", ValueFromPipelineByPropertyName = true)] - [ValidateNotNullOrEmpty] - public string ProjectName { get; set; } - - protected override void BeginProcessing() - { - InitialiseProject(); - } - - protected override void ProcessRecord() - { - IEnumerable items = project.ProjectItems; - var configExistsInProject = items.Cast().Any(item => - { - var name = (string)item.Name; - return name.Equals("App.config", StringComparison.OrdinalIgnoreCase) || - name.Equals("Web.config", StringComparison.OrdinalIgnoreCase); - }); - - string projectPath = Path.GetDirectoryName(project.FullName); - var configFilePath = Path.Combine(projectPath, GetConfigFileForProjectType()); - - // I would have liked to use: - //( Get-Project).DTE.ItemOperations.AddNewItem("Visual C# Items\General\Application Configuration File") - // but for some reason it doesn't work! - - var doc = GetOrCreateDocument(configFilePath); - - CreateConfigSectionIfRequired(doc); - - ModifyConfig(doc); - - doc.Save(configFilePath); - - if (!configExistsInProject) - { - project.ProjectItems.AddFromFile(configFilePath); - } - } - - void InitialiseProject() - { - var getProjectResults = InvokeCommand.InvokeScript(string.Format("Get-Project {0}", ProjectName)).ToList(); - project = getProjectResults.Count == 1 ? getProjectResults.Single().BaseObject : null; - } - - public abstract void ModifyConfig(XDocument doc); - - static void CreateConfigSectionIfRequired(XDocument doc) - { - if (doc.Root == null) - { - doc.Add(new XElement("/configuration")); - } - if (doc.XPathSelectElement("/configuration/configSections") == null) - { - doc.Root.AddFirst(new XElement("configSections")); - } - } - - static XDocument GetOrCreateDocument(string path) - { - if (File.Exists(path)) - { - try - { - return GetDocument(path); - } - catch (FileNotFoundException) - { - return CreateDocument(path); - } - catch (XmlException) - { - return CreateDocument(path); - } - } - return CreateDocument(path); - } - - static XDocument CreateDocument(string path) - { - var document = new XDocument(new XElement("configuration")) - { - Declaration = new XDeclaration("1.0", "utf-8", "yes") - }; - - document.Save(path); - - return document; - } - - static XDocument GetDocument(string path) - { - using (Stream configStream = File.Open(path, FileMode.Open)) - { - return XDocument.Load(configStream); - } - } - - private dynamic project; - - string GetConfigFileForProjectType() - { - if (IsWebProject()) - { - return "Web.config"; - } - - return "App.config"; - } - - bool IsWebProject() - { - var types = new HashSet(GetProjectTypeGuids(), StringComparer.OrdinalIgnoreCase); - return types.Contains(VsConstants.WebSiteProjectTypeGuid) || types.Contains(VsConstants.WebApplicationProjectTypeGuid); - } - - IEnumerable GetProjectTypeGuids() - { - var projectTypeGuids = Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.GetLoadedProjects((string)project.FullName).Single().GetPropertyValue("ProjectTypeGuids"); - - if (String.IsNullOrEmpty(projectTypeGuids)) - return Enumerable.Empty(); - - return projectTypeGuids.Split(';'); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddLoggingConfig.cs b/src/NServiceBus.PowerShell.Development/AddLoggingConfig.cs deleted file mode 100644 index d3a3cead2af..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddLoggingConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusLoggingConfig")] - public class AddLoggingConfig : AddConfigSection - { - const string Instructions = @""; - - public override void ModifyConfig(XDocument doc) - { - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='Logging' and @type='NServiceBus.Config.Logging, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", "Logging"), - new XAttribute("type", "NServiceBus.Config.Logging, NServiceBus.Core"))); - } - - var forwardingElement = doc.XPathSelectElement("/configuration/Logging"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - new XElement("Logging", - new XAttribute("Threshold", "INFO"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddMasterNodeConfig.cs b/src/NServiceBus.PowerShell.Development/AddMasterNodeConfig.cs deleted file mode 100644 index 000260101ca..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddMasterNodeConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusMasterNodeConfig")] - public class AddMasterNodeConfig : AddConfigSection - { - const string Instructions = @""; - - public override void ModifyConfig(XDocument doc) - { - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='MasterNodeConfig' and @type='NServiceBus.Config.MasterNodeConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", "MasterNodeConfig"), - new XAttribute("type", "NServiceBus.Config.MasterNodeConfig, NServiceBus.Core"))); - } - - var forwardingElement = doc.XPathSelectElement("/configuration/MasterNodeConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - new XElement("MasterNodeConfig", - new XAttribute("Node", "SERVER_NAME_HERE"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddMessageForwardingInCaseOfFaultConfig.cs b/src/NServiceBus.PowerShell.Development/AddMessageForwardingInCaseOfFaultConfig.cs deleted file mode 100644 index a938ef0644a..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddMessageForwardingInCaseOfFaultConfig.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusMessageForwardingInCaseOfFaultConfig")] - public class AddMessageForwardingInCaseOfFaultConfig : AddConfigSection - { - public override void ModifyConfig(XDocument doc) - { - const string Instructions = @""; - - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='MessageForwardingInCaseOfFaultConfig' and @type='NServiceBus.Config.MessageForwardingInCaseOfFaultConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", - "MessageForwardingInCaseOfFaultConfig"), - new XAttribute("type", - "NServiceBus.Config.MessageForwardingInCaseOfFaultConfig, NServiceBus.Core"))); - - } - - var forwardingElement = doc.XPathSelectElement("/configuration/MessageForwardingInCaseOfFaultConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - new XElement("MessageForwardingInCaseOfFaultConfig", - new XAttribute("ErrorQueue", "error"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddNHibernateConfig.cs b/src/NServiceBus.PowerShell.Development/AddNHibernateConfig.cs deleted file mode 100644 index aca014e1a30..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddNHibernateConfig.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - - [Cmdlet(VerbsCommon.Add, "NServiceBusNHibernateConfig")] - public class AddNHibernateConfig : AddConfigSection - { - private const string Instructions = - @" -To run NServiceBus with NHibernate you need to at least specify the database connectionstring. -Here is an example of what is required: - - - - - - - - - - - - - - - - - - - - - "; - - public override void ModifyConfig(XDocument doc) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions)); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddSecondLevelRetriesConfig.cs b/src/NServiceBus.PowerShell.Development/AddSecondLevelRetriesConfig.cs deleted file mode 100644 index 24500a94557..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddSecondLevelRetriesConfig.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusSecondLevelRetriesConfig")] - public class AddSecondLevelRetriesConfig : AddConfigSection - { - const string Instructions = @""; - - public override void ModifyConfig(XDocument doc) - { - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='SecondLevelRetriesConfig' and @type='NServiceBus.Config.SecondLevelRetriesConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", "SecondLevelRetriesConfig"), - new XAttribute("type", "NServiceBus.Config.SecondLevelRetriesConfig, NServiceBus.Core"))); - } - - var forwardingElement = doc.XPathSelectElement("/configuration/SecondLevelRetriesConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - new XElement("SecondLevelRetriesConfig", - new XAttribute("Enabled", "true"), - new XAttribute("NumberOfRetries", "3"), - new XAttribute("TimeIncrease", "00:00:10"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddTransportConfig.cs b/src/NServiceBus.PowerShell.Development/AddTransportConfig.cs deleted file mode 100644 index d6e8b490677..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddTransportConfig.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusTransportConfig")] - public class AddTransportConfig : AddConfigSection - { - const string Instructions = @""; - - public override void ModifyConfig(XDocument doc) - { - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='TransportConfig' and @type='NServiceBus.Config.TransportConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", "TransportConfig"), - new XAttribute("type", "NServiceBus.Config.TransportConfig, NServiceBus.Core"))); - } - - var forwardingElement = doc.XPathSelectElement("/configuration/TransportConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf(new XComment(Instructions), - new XElement("TransportConfig", - new XAttribute("MaxRetries", "5"), - new XAttribute("MaximumConcurrencyLevel", "1"), - new XAttribute("MaximumMessageThroughputPerSecond", "0"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/AddUnicastBusConfig.cs b/src/NServiceBus.PowerShell.Development/AddUnicastBusConfig.cs deleted file mode 100644 index 30848507acb..00000000000 --- a/src/NServiceBus.PowerShell.Development/AddUnicastBusConfig.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - using System.Xml.Linq; - using System.Xml.XPath; - - [Cmdlet(VerbsCommon.Add, "NServiceBusUnicastBusConfig")] - public class AddUnicastBusConfig : AddConfigSection - { - const string Instructions = @" - - To register all message types defined in an assembly: - - - To register all message types defined in an assembly with a specific namespace (it does not include sub namespaces): - - - To register a specific type in an assembly: - - - "; - - public override void ModifyConfig(XDocument doc) - { - var sectionElement = doc.XPathSelectElement("/configuration/configSections/section[@name='UnicastBusConfig' and @type='NServiceBus.Config.UnicastBusConfig, NServiceBus.Core']"); - if (sectionElement == null) - { - - doc.XPathSelectElement("/configuration/configSections").Add(new XElement("section", - new XAttribute("name", - "UnicastBusConfig"), - new XAttribute("type", - "NServiceBus.Config.UnicastBusConfig, NServiceBus.Core"))); - - } - - var forwardingElement = doc.XPathSelectElement("/configuration/UnicastBusConfig"); - if (forwardingElement == null) - { - doc.Root.LastNode.AddAfterSelf( - new XComment(Instructions), - new XElement("UnicastBusConfig", - new XElement("MessageEndpointMappings"))); - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/CmdletBase.cs b/src/NServiceBus.PowerShell.Development/CmdletBase.cs deleted file mode 100644 index 88cc1131998..00000000000 --- a/src/NServiceBus.PowerShell.Development/CmdletBase.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace NServiceBus.PowerShell -{ - using System.Management.Automation; - - public abstract class CmdletBase : PSCmdlet - { - - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/InternalsVisibleTo.cs b/src/NServiceBus.PowerShell.Development/InternalsVisibleTo.cs deleted file mode 100644 index 3d9415e34a3..00000000000 --- a/src/NServiceBus.PowerShell.Development/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("NServiceBus.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.csproj b/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.csproj deleted file mode 100644 index 8d695d16625..00000000000 --- a/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.csproj +++ /dev/null @@ -1,91 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {5E51EFBF-329F-4D3A-B86E-CC111697746F} - Library - true - ..\NServiceBus.snk - Properties - NServiceBus.PowerShell.Development - NServiceBus.PowerShell.Development - v4.5 - 512 - - ..\ - - - False - - - true - full - false - ..\..\binaries\ - DEBUG;TRACE - prompt - 4 - true - false - - - pdbonly - true - ..\..\binaries\ - TRACE - prompt - 4 - true - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - PreserveNewest - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.psd1 b/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.psd1 deleted file mode 100644 index f7bc65b32dc..00000000000 Binary files a/src/NServiceBus.PowerShell.Development/NServiceBus.PowerShell.Development.psd1 and /dev/null differ diff --git a/src/NServiceBus.PowerShell.Development/Properties/AssemblyInfo.cs b/src/NServiceBus.PowerShell.Development/Properties/AssemblyInfo.cs deleted file mode 100644 index 72c015c9e8a..00000000000 --- a/src/NServiceBus.PowerShell.Development/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NServiceBus.PowerShell")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCopyright("Copyright 2010-2013 NServiceBus. All rights reserved")] -[assembly: AssemblyProduct("NServiceBus")] -[assembly: AssemblyCompany("NServiceBus Ltd.")] -[assembly: AssemblyConfiguration("release")] -[assembly: ComVisible(false)] - diff --git a/src/NServiceBus.PowerShell.Development/VsConstants.cs b/src/NServiceBus.PowerShell.Development/VsConstants.cs deleted file mode 100644 index c755917d93c..00000000000 --- a/src/NServiceBus.PowerShell.Development/VsConstants.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NServiceBus.PowerShell -{ - internal static class VsConstants - { - // Project type guids - internal const string WebApplicationProjectTypeGuid = "{349C5851-65DF-11DA-9384-00065B846F21}"; - internal const string WebSiteProjectTypeGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}"; - internal const string CsharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; - internal const string VbProjectTypeGuid = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}"; - - internal const string Mvc1ProjectTypeGuid = "{603C0E0B-DB56-11DC-BE95-000D561079B0}"; - internal const string Mvc2ProjectTypeGuid = "{F85E285D-A4E0-4152-9332-AB1D724D3325}"; - internal const string Mvc3ProjectTypeGuid = "{E53F8FEA-EAE0-44A6-8774-FFD645390401}"; - - internal const string CsharpCodeModelLanguageGuid = "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}"; - internal const string VbCodeModelLanguageGuid = "{B5E9BD33-6D3E-4B5D-925E-8A43B79820B4}"; - - internal const int S_OK = 0; - } -} \ No newline at end of file diff --git a/src/NServiceBus.PowerShell.Development/about_NServiceBus.help.txt b/src/NServiceBus.PowerShell.Development/about_NServiceBus.help.txt deleted file mode 100644 index c093a536e24..00000000000 --- a/src/NServiceBus.PowerShell.Development/about_NServiceBus.help.txt +++ /dev/null @@ -1,50 +0,0 @@ -TOPIC - about_NServiceBus - -SHORT DESCRIPTION - Provides information about NServiceBus commands. - -LONG DESCRIPTION - This topic describes the NServiceBus commands. - http://docs.particular.net/ - - The following NServiceBus cmdlets are included: - - Cmdlet Description - ------------------------------------------------------------------------------------------------- - Add-NServiceBusMessageForwardingInCaseOfFaultConfig Adds the required configuration section to - the config file. - - Add-NServiceBusUnicastBusConfig Adds the required configuration section to - the config file. - - Add-NServiceBusTransportConfig Adds the required configuration section to - the config file. - - Add-NServiceBusSecondLevelRetriesConfig Adds the required configuration section to - the config file. - - Add-NServiceBusLoggingConfig Adds the required configuration section to - the config file. - - Add-NServiceBusMasterNodeConfig Adds the required configuration section to - the config file. - - Add-NServiceBusNHibernateConfig Adds the NHibernate supported config settings - as a comment. - - -Sample Commands - - To add a transport config section in your app/web.config - - PackageManagerConsole> Add-NServiceBusTransportConfig - -SEE ALSO - Add-NServiceBusMessageForwardingInCaseOfFaultConfig - Add-NServiceBusUnicastBusConfig - Add-NServiceBusTransportConfig - Add-NServiceBusSecondLevelRetriesConfig - Add-NServiceBusLoggingConfig - Add-NServiceBusMasterNodeConfig - Add-NServiceBusNHibernateConfig diff --git a/src/NServiceBus.PowerShell.Development/packages.config b/src/NServiceBus.PowerShell.Development/packages.config deleted file mode 100644 index b8939aa8a0d..00000000000 --- a/src/NServiceBus.PowerShell.Development/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexType.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexType.cs deleted file mode 100644 index caf8208d2d7..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexType.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.Collections.Generic; - using System.Reflection; - - public class ComplexType - { - private readonly IList elements = new List(); - - private ComplexType() - { - TimeToBeReceived = TimeSpan.Zero; - } - - public string Name { get; private set; } - - public string BaseName { get; private set; } - - public TimeSpan TimeToBeReceived { get; private set; } - - public IEnumerable Elements - { - get { return elements; } - } - - public static ComplexType Scan(Type type) - { - if (type.IsPrimitive || type == typeof(string) || type == typeof(object) || type == typeof(Guid) || type == typeof(DateTime) || type == typeof(TimeSpan) || type == typeof(DateTimeOffset) || type.IsEnum || type == typeof(Decimal)) - return null; - - var complex = new ComplexType - { - Name = Reflect.GetTypeNameFrom(type) - }; - - if (Repository.IsNormalizedList(type)) - { - var enumerated = Reflect.GetEnumeratedTypeFrom(type); - - var e = Element.Scan(enumerated, Reflect.GetTypeNameFrom(enumerated)); - - if (e != null) - { - e.UnboundMaxOccurs(); - e.MakeNillable(); - - complex.elements.Add(e); - } - - Repository.Handle(enumerated); - } - else - { - Type baseType = null; - - if (!type.IsInterface) - if (type.BaseType != typeof (object) && type.BaseType != typeof(ValueType) && type.BaseType != null) - baseType = type.BaseType; - - if (type.IsInterface) - { - foreach(var i in type.GetInterfaces()) - { - if (i != typeof(IMessage)) - { - baseType = i; - break; - } - } - } - - var propsToIgnore = new List(); - - if (baseType != null) - { - complex.BaseName = baseType.Name; - propsToIgnore = new List(baseType.GetProperties()); - } - - foreach (var prop in type.GetProperties()) - { - if (IsInList(prop, propsToIgnore)) - continue; - - if (!IsKeyValuePair(type) && (!prop.CanRead || !prop.CanWrite)) - continue; - - Repository.Handle(prop.PropertyType); - - var e = Element.Scan(prop.PropertyType, prop.Name); - - if (e != null) - complex.elements.Add(e); - } - } - - foreach(TimeToBeReceivedAttribute a in type.GetCustomAttributes(typeof(TimeToBeReceivedAttribute), true)) - complex.TimeToBeReceived = a.TimeToBeReceived; - - return complex; - } - - private static bool IsKeyValuePair(Type t) - { - var args = t.GetGenericArguments(); - if (args.Length != 2) - return false; - return (typeof(KeyValuePair<,>).MakeGenericType(args[0], args[1]) == t); - } - - private static bool IsInList(PropertyInfo prop, ICollection propsToIgnore) - { - if (propsToIgnore.Contains(prop)) - return true; - - foreach(var pi in propsToIgnore) - if (pi.Name == prop.Name) - return true; - - return false; - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexTypeWriter.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexTypeWriter.cs deleted file mode 100644 index 1764bc865b6..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/ComplexTypeWriter.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System.Text; - - public class ComplexTypeWriter - { - private readonly ComplexType complex; - private readonly string beginTopFormat = "\n"; - private readonly string beginBaseFormat = "\n\n"; - private readonly string beginSequence = ""; - private readonly string endSequence = ""; - private readonly string endBaseFormat = "\n\n"; - private readonly string endTopFormat = "\n"; - - public static void Write(ComplexType complex, StringBuilder builder) - { - new ComplexTypeWriter(complex).Write(builder); - } - - private ComplexTypeWriter(ComplexType complex) - { - this.complex = complex; - } - - public void Write(StringBuilder builder) - { - builder.AppendFormat(beginTopFormat, complex.Name); - - if (complex.BaseName != null) - builder.AppendFormat(beginBaseFormat, complex.BaseName); - - builder.AppendLine(beginSequence); - - foreach (var e in complex.Elements) - builder.AppendFormat(ElementWriter.Write(e)); - - builder.AppendLine(endSequence); - - if (complex.BaseName != null) - builder.AppendFormat(endBaseFormat); - - builder.AppendFormat(endTopFormat); - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Element.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Element.cs deleted file mode 100644 index 2399fc567cf..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Element.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.Collections; - - public class Element - { - private string name; - private int minOccurs = 1; - private bool unboundedMaxOccurs; - private bool nillable; - private string type; - private string nameSpace; - - public string Name - { - get { return name; } - } - - public int MinOccurs - { - get { return minOccurs; } - } - - public bool UnboundedMaxOccurs - { - get { return unboundedMaxOccurs; } - } - - public bool Nillable - { - get { return nillable; } - } - - public string Type - { - get { return type; } - } - - public string NameSpace - { - get { return nameSpace; } - } - - public void DoesNotNeedToOccur() - { - minOccurs = 0; - } - - public void UnboundMaxOccurs() - { - unboundedMaxOccurs = true; - } - - public void MakeNillable() - { - nillable = true; - } - - private Element() - { - - } - - public static Element Scan(Type t, string name) - { - var e = new Element(); - - if (t == typeof(Guid)) - { - Events.FoundGuid(); - e.nameSpace = "http://microsoft.com/wsdl/types/"; - } - - if (typeof(IEnumerable).IsAssignableFrom(t) || t.IsClass || t.IsInterface) - e.DoesNotNeedToOccur(); - - e.type = Reflect.GetTypeNameFrom(t); - e.name = name; - - return e; - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/ElementWriter.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/ElementWriter.cs deleted file mode 100644 index 245b8119fb1..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/ElementWriter.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System.Text; - - public class ElementWriter - { - private readonly Element e; - private readonly string startFormat = "\n"; - - public static string Write(Element e) - { - return new ElementWriter(e).Write(); - } - - private ElementWriter(Element e) - { - this.e = e; - } - - public string Write() - { - var builder = new StringBuilder(); - - builder.AppendFormat(startFormat, e.MinOccurs); - - if (e.UnboundedMaxOccurs) - builder.Append("unbounded"); - else - builder.Append("1"); - - builder.AppendFormat(nameFormat, e.Name); - - if (e.Nillable) - builder.Append("nillable=\"true\" "); - - if (e.NameSpace != null) - builder.AppendFormat(namespaceFormat, e.NameSpace); - - builder.AppendFormat(typeFormat, e.Type); - - return builder.ToString(); - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Events.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Events.cs deleted file mode 100644 index 0016a7a2eb4..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Events.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - - public static class Events - { - public static event EventHandler GuidDetected; - public static void FoundGuid() - { - if (GuidDetected != null) - GuidDetected(null, null); - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/NServiceBus.Serializers.XML.XsdGenerator.csproj b/src/NServiceBus.Serializers.XML.XsdGenerator/NServiceBus.Serializers.XML.XsdGenerator.csproj deleted file mode 100644 index 2c4394d8830..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/NServiceBus.Serializers.XML.XsdGenerator.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB} - Exe - Properties - NServiceBus.Serializers.XML.XsdGenerator - XsdGenerator - v4.5 - true - ..\NServiceBus.snk - ..\ - - False - - - true - full - false - ..\..\binaries\Tools\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - true - false - - - pdbonly - true - ..\..\binaries\Tools\ - TRACE - prompt - 4 - AllRules.ruleset - true - false - - - - - - - - - - - ExtensionMethods.cs - - - - - - - - - - - - - - - - {DD48B2D0-E996-412D-9157-821ED8B17A9D} - NServiceBus.Core - - - - - - - \ No newline at end of file diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Program.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Program.cs deleted file mode 100644 index 9c4c965b2fc..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Program.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.IO; - using System.Reflection; - using System.Text; - - class Program - { - static void Main(string[] args) - { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - var a = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, args[0])); - - if (args.Length == 2) - baseNameSpace = args[1]; - - Events.GuidDetected += delegate - { - needToGenerateGuid = true; - }; - - foreach(var t in a.GetTypes()) - TopLevelScan(t); - - var xsd = GenerateXsdString(); - - using(var writer = File.CreateText(GetFileName())) - writer.Write(xsd); - - if (needToGenerateGuid) - using (var writer = File.CreateText(GetFileName())) - writer.Write(Strings.GuidXsd); - } - - static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Console.WriteLine("Usage: first parameter [required], your assembly. second parameter [optional] the base namespace (or http://tempuri.net will be used)."); - } - - private static string GetFileName() - { - var i = 0; - while (File.Exists(string.Format("schema{0}.xsd", i))) - i++; - - return string.Format("schema{0}.xsd", i); - } - - private static string GenerateXsdString() - { - var builder = new StringBuilder(); - - builder.AppendLine(""); - builder.AppendLine(""); - - if (needToGenerateGuid) - builder.AppendLine(""); - - foreach (var complex in Repository.ComplexTypes) - { - builder.AppendFormat("\n", complex.Name); - - if (complex.TimeToBeReceived > TimeSpan.Zero) - { - builder.AppendLine(""); - builder.AppendLine(""); - builder.AppendFormat("{0}\n", complex.TimeToBeReceived); - builder.AppendLine(""); - builder.AppendLine(""); - } - - builder.AppendLine(""); - ComplexTypeWriter.Write(complex, builder); - } - - foreach (var simple in Repository.SimpleTypes) - { - builder.AppendFormat("\n", simple.Name); - SimpleTypeWriter.Write(simple, builder); - } - - SimpleTypeWriter.WriteChar(builder); - - builder.AppendLine(""); - - var result = builder.ToString(); - - return result; - } - - public static void TopLevelScan(Type type) - { - if (typeof(IMessage).IsAssignableFrom(type)) - { - if (nameSpace == null) - nameSpace = type.Namespace; - else - if (type.Namespace != nameSpace) - { - Console.WriteLine("WARNING: Not all types are in the same namespace. This may cause serialization to fail and is not supported."); - Console.ReadLine(); - - throw new InvalidOperationException("Not all types are in the same namespace"); - } - - Scan(type); - } - } - - public static void Scan(Type type) - { - if (type == null || type == typeof(object) || type == typeof(IMessage)) - return; - - Repository.Handle(type); - - if (!type.IsInterface) - Scan(type.BaseType); - else - foreach (var i in type.GetInterfaces()) - Scan(i); - } - - private static bool needToGenerateGuid; - private static string nameSpace; - private static string baseNameSpace = "http://tempuri.net"; - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Properties/AssemblyInfo.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Properties/AssemblyInfo.cs deleted file mode 100644 index d259e8bcbeb..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NServiceBus XSD Generator")] -[assembly: AssemblyDescription("XSD file generator for interface-based message schemas.")] -[assembly: AssemblyCopyright("Copyright 2010-2014 NServiceBus. All rights reserved")] -[assembly: AssemblyProduct("NServiceBus")] -[assembly: AssemblyCompany("NServiceBus Ltd.")] -[assembly: ComVisible(false)] diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Reflect.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Reflect.cs deleted file mode 100644 index bb59cd4ddda..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Reflect.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.Collections.Generic; - using Utils.Reflection; - - static class Reflect - { - public static string GetTypeNameFrom(Type t) - { - if (t == typeof(int)) - return "xs:int"; - if (t == typeof(string)) - return "xs:string"; - if (t == typeof(double)) - return "xs:double"; - if (t == typeof(float)) - return "xs:float"; - if (t == typeof(bool)) - return "xs:boolean"; - if (t == typeof(Guid)) - return Strings.NamespacePrefix + ":guid"; - if (t == typeof(DateTime)) - return "xs:dateTime"; - if (t == typeof(TimeSpan)) - return "xs:duration"; - if (t == typeof(decimal)) - return "xs:decimal"; - if (t == typeof(DateTimeOffset)) - return "xs:dateTime"; - if (t == typeof(Object)) - return "xs:anyType"; - - var arrayType = GetEnumeratedTypeFrom(t); - - if (arrayType != null) - return Strings.ArrayOf + GetTypeNameFrom(arrayType); - - return t.SerializationFriendlyName(); - } - - public static Type GetEnumeratedTypeFrom(Type t) - { - if (t.IsArray) - return t.GetElementType(); - - foreach (var interfaceType in t.GetInterfaces()) - { - var genericArgs = interfaceType.GetGenericArguments(); - if (genericArgs.Length != 1) - continue; - - if (typeof(IEnumerable<>).MakeGenericType(genericArgs[0]).IsAssignableFrom(t)) - return genericArgs[0]; - } - - return null; - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Repository.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Repository.cs deleted file mode 100644 index 83b5450f6e3..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Repository.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.Collections; - using System.Collections.Generic; - - public static class Repository - { - /// - /// Returns types in the order they were handled - /// - public static IEnumerable ComplexTypes - { - get - { - var keys = new List(types.Keys); - for (var i = keys.Count - 1; i >= 0; i-- ) - yield return types[keys[i]]; - } - } - - public static IEnumerable SimpleTypes - { - get { return simpleTypesToCreate; } - } - - public static void Handle(Type type) - { - var normalized = Normalize(type); - if (normalized != type) - { - Handle(normalized); - return; - } - - if (types.ContainsKey(type)) - return; - - if (simpleTypesToCreate.Contains(type)) - return; - - var complex = ComplexType.Scan(type); - if (complex != null) - { - types[type] = complex; - return; - } - - if (type.IsEnum) - if (!simpleTypesToCreate.Contains(type)) - simpleTypesToCreate.Add(type); - } - - public static bool IsNormalizedList(Type type) - { - foreach (var interfaceType in type.GetInterfaces()) - { - var genericArgs = interfaceType.GetGenericArguments(); - if (genericArgs.Length != 1) - continue; - - if (typeof(IEnumerable<>).MakeGenericType(genericArgs[0]) == interfaceType) - return true; - } - - return false; - } - - private static Type Normalize(Type type) - { - if (!typeof(IEnumerable).IsAssignableFrom(type)) - return type; - - var enumerated = Reflect.GetEnumeratedTypeFrom(type); - if (enumerated == null) - return type; - - return typeof(List<>).MakeGenericType(enumerated); - } - - private static readonly IDictionary types = new Dictionary(); - private static readonly IList simpleTypesToCreate = new List(); - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/SimpleTypeWriter.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/SimpleTypeWriter.cs deleted file mode 100644 index cbb9ce8d020..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/SimpleTypeWriter.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - using System; - using System.Text; - - public class SimpleTypeWriter - { - public static void Write(Type t, StringBuilder builder) - { - if (!t.IsEnum) - return; - - builder.AppendFormat("\n", t.Name); - builder.AppendLine(""); - - foreach(var val in Enum.GetNames(t)) - builder.AppendFormat("\n", val); - - builder.AppendLine(""); - builder.AppendLine(""); - } - - public static void WriteChar(StringBuilder builder) - { - builder.Append("\n\n\n\n\n\n"); - } - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/Strings.cs b/src/NServiceBus.Serializers.XML.XsdGenerator/Strings.cs deleted file mode 100644 index 62a75732a74..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/Strings.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace NServiceBus.Serializers.XML.XsdGenerator -{ - public static class Strings - { - public static readonly string ArrayOf = "ArrayOf"; - public static readonly string NamespacePrefix = "q1"; - - public static readonly string GuidXsd = "\n" + - "\n" + - "\t\n" + - "\t\t\n" + - "\t\t\t\n" + - "\t\t\n" + - "\t\n" + - "\n"; - } -} diff --git a/src/NServiceBus.Serializers.XML.XsdGenerator/app.config b/src/NServiceBus.Serializers.XML.XsdGenerator/app.config deleted file mode 100644 index c5e1daefd3d..00000000000 --- a/src/NServiceBus.Serializers.XML.XsdGenerator/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/NServiceBus.Testing.Fakes/DefaultTestingLoggerFactory.cs b/src/NServiceBus.Testing.Fakes/DefaultTestingLoggerFactory.cs new file mode 100644 index 00000000000..ff6de48693b --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/DefaultTestingLoggerFactory.cs @@ -0,0 +1,62 @@ +namespace NServiceBus.Testing +{ + using System; + using System.IO; + using Logging; + + class DefaultTestingLoggerFactory : ILoggerFactory + { + public DefaultTestingLoggerFactory(LogLevel filterLevel, TextWriter textWriter) + { + this.filterLevel = filterLevel; + textWriterLogger = new TextWriterLogger(textWriter); + isDebugEnabled = LogLevel.Debug >= filterLevel; + isInfoEnabled = LogLevel.Info >= filterLevel; + isWarnEnabled = LogLevel.Warn >= filterLevel; + isErrorEnabled = LogLevel.Error >= filterLevel; + isFatalEnabled = LogLevel.Fatal >= filterLevel; + } + + public ILog GetLogger(Type type) + { + return GetLogger(type.FullName); + } + + public ILog GetLogger(string name) + { + return new NamedLogger(name, this) + { + IsDebugEnabled = isDebugEnabled, + IsInfoEnabled = isInfoEnabled, + IsWarnEnabled = isWarnEnabled, + IsErrorEnabled = isErrorEnabled, + IsFatalEnabled = isFatalEnabled + }; + } + + public void Write(string name, LogLevel messageLevel, string message) + { + if (messageLevel < filterLevel) + { + return; + } + var datePart = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); + var paddedLevel = messageLevel.ToString().ToUpper().PadRight(5); + var fullMessage = $"{datePart} {paddedLevel} {name} {message}"; + lock (locker) + { + textWriterLogger.Write(fullMessage); + } + } + + LogLevel filterLevel; + bool isDebugEnabled; + bool isErrorEnabled; + bool isFatalEnabled; + bool isInfoEnabled; + bool isWarnEnabled; + + object locker = new object(); + TextWriterLogger textWriterLogger; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/FakeBuilder.cs b/src/NServiceBus.Testing.Fakes/FakeBuilder.cs new file mode 100644 index 00000000000..f34d71bcf23 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/FakeBuilder.cs @@ -0,0 +1,155 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using ObjectBuilder; + + /// + /// A fake implementation of for testing purposes. + /// + public partial class FakeBuilder : IBuilder + { + /// + /// Returns an instantiation of the given type. + /// + /// The to build. + /// + /// The component instance. + /// + public virtual object Build(Type typeToBuild) + { + return BuildAll(typeToBuild).First(); + } + + /// + /// Creates an instance of the given type, injecting it with all defined dependencies. + /// + /// Type to be resolved. + /// + /// Instance of . + /// + public virtual T Build() + { + return BuildAll().First(); + } + + /// + /// For each type that is compatible with the given type, an instance is created with all dependencies injected. + /// + /// The to build. + /// + /// The component instances. + /// + public virtual IEnumerable BuildAll(Type typeToBuild) + { + if (instances.ContainsKey(typeToBuild)) + { + return instances[typeToBuild]; + } + + if (factories.ContainsKey(typeToBuild)) + { + return factories[typeToBuild](); + } + + throw new Exception($"Cannot build instance of type {typeToBuild} because there was no instance of factory registered for it."); + } + + /// + /// For each type that is compatible with T, an instance is created with all dependencies injected, and yielded to the + /// caller. + /// + /// Type to be resolved. + /// + /// Instances of . + /// + public virtual IEnumerable BuildAll() + { + var type = typeof(T); + + if (instances.ContainsKey(type)) + { + return instances[type].Cast(); + } + + if (factories.ContainsKey(type)) + { + return factories[type]().Cast(); + } + + throw new Exception($"Cannot build instance of type {type} because there was no instance of factory registered for it."); + } + + /// + /// Builds an instance of the defined type injecting it with all defined dependencies + /// and invokes the given action on the instance. + /// + /// The to build. + /// The callback to call. + public virtual void BuildAndDispatch(Type typeToBuild, Action action) + { + throw new NotImplementedException(); + } + + /// + /// Returns a child instance of the container to facilitate deterministic disposal + /// of all resources built by the child container. + /// + /// + /// Returns a new child container. + /// + public virtual IBuilder CreateChildBuilder() + { + return this; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// 2 + public virtual void Dispose() + { + } + + /// + /// Releases a component instance. + /// + /// The component instance to release. + public virtual void Release(object instance) + { + } + + /// + /// Registers an instance which will be returned every time an instance of is resolved. + /// + public void Register(params T[] instance) where T : class + { + instances.Add(typeof(T), instance); + } + + /// + /// Registers a factory method which will be invoked every time an instance of is resolved. + /// + public void Register(Func factory) + { + factories.Add(typeof(T), () => new object[] + { + factory() + }); + } + + /// + /// Registers a factory method which will be invoked every time an instance of is resolved. + /// + public void Register(Func factory) where T : class + { + factories.Add(typeof(T), factory); + } + + Dictionary> factories = new Dictionary>(); + + Dictionary instances = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj b/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj new file mode 100644 index 00000000000..56326893abd --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj @@ -0,0 +1,114 @@ + + + + + Debug + AnyCPU + {80105366-8EF9-494D-A296-E100E82224CF} + Library + Properties + NServiceBus.Testing + NServiceBus.Testing.Fakes + v4.5.2 + 512 + + + + + true + full + false + ..\..\binaries\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + ..\..\binaries\NServiceBus.Testing.Fakes.xml + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + ..\Test.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {dd48b2d0-e996-412d-9157-821ed8b17a9d} + NServiceBus.Core + + + + + nservicebus.testing.fakes.nuspec + + + Designer + + + Test.snk + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj.DotSettings b/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj.DotSettings new file mode 100644 index 00000000000..b233cd097ac --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/NServiceBus.Testing.Fakes.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/NamedLogger.cs b/src/NServiceBus.Testing.Fakes/NamedLogger.cs new file mode 100644 index 00000000000..41a6e7a1490 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/NamedLogger.cs @@ -0,0 +1,98 @@ +namespace NServiceBus.Testing +{ + using System; + using Logging; + + class NamedLogger : ILog + { + public NamedLogger(string name, DefaultTestingLoggerFactory defaultLoggerFactory) + { + this.name = name; + this.defaultLoggerFactory = defaultLoggerFactory; + } + + public bool IsDebugEnabled { get; internal set; } + public bool IsInfoEnabled { get; internal set; } + public bool IsWarnEnabled { get; internal set; } + public bool IsErrorEnabled { get; internal set; } + public bool IsFatalEnabled { get; internal set; } + + public void Debug(string message) + { + defaultLoggerFactory.Write(name, LogLevel.Debug, message); + } + + public void Debug(string message, Exception exception) + { + defaultLoggerFactory.Write(name, LogLevel.Debug, message + Environment.NewLine + exception); + } + + public void DebugFormat(string format, params object[] args) + { + defaultLoggerFactory.Write(name, LogLevel.Debug, string.Format(format, args)); + } + + public void Info(string message) + { + defaultLoggerFactory.Write(name, LogLevel.Info, message); + } + + public void Info(string message, Exception exception) + { + defaultLoggerFactory.Write(name, LogLevel.Info, message + Environment.NewLine + exception); + } + + public void InfoFormat(string format, params object[] args) + { + defaultLoggerFactory.Write(name, LogLevel.Info, string.Format(format, args)); + } + + public void Warn(string message) + { + defaultLoggerFactory.Write(name, LogLevel.Warn, message); + } + + public void Warn(string message, Exception exception) + { + defaultLoggerFactory.Write(name, LogLevel.Warn, message + Environment.NewLine + exception); + } + + public void WarnFormat(string format, params object[] args) + { + defaultLoggerFactory.Write(name, LogLevel.Warn, string.Format(format, args)); + } + + public void Error(string message) + { + defaultLoggerFactory.Write(name, LogLevel.Error, message); + } + + public void Error(string message, Exception exception) + { + defaultLoggerFactory.Write(name, LogLevel.Error, message + Environment.NewLine + exception); + } + + public void ErrorFormat(string format, params object[] args) + { + defaultLoggerFactory.Write(name, LogLevel.Error, string.Format(format, args)); + } + + public void Fatal(string message) + { + defaultLoggerFactory.Write(name, LogLevel.Fatal, message); + } + + public void Fatal(string message, Exception exception) + { + defaultLoggerFactory.Write(name, LogLevel.Error, message + Environment.NewLine + exception); + } + + public void FatalFormat(string format, params object[] args) + { + defaultLoggerFactory.Write(name, LogLevel.Fatal, string.Format(format, args)); + } + + DefaultTestingLoggerFactory defaultLoggerFactory; + string name; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/OutgoingMessage.cs b/src/NServiceBus.Testing.Fakes/OutgoingMessage.cs new file mode 100644 index 00000000000..42933e22ef4 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/OutgoingMessage.cs @@ -0,0 +1,31 @@ +namespace NServiceBus.Testing +{ + using Extensibility; + + /// + /// Represents an outgoing message. Contains the message itself and its associated options. + /// + /// The message type. + /// The options type of the message. + public class OutgoingMessage where TOptions : ExtendableOptions + { + /// + /// Creates a new instance for the given message and options. + /// + protected OutgoingMessage(TMessage message, TOptions options) + { + Message = message; + Options = options; + } + + /// + /// The outgoing message. + /// + public TMessage Message { get; } + + /// + /// The options of the outgoing message. + /// + public TOptions Options { get; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/OutgoingMessageExtensions.cs b/src/NServiceBus.Testing.Fakes/OutgoingMessageExtensions.cs new file mode 100644 index 00000000000..ca167470adc --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/OutgoingMessageExtensions.cs @@ -0,0 +1,67 @@ +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Extension methods for easier type safe access to instances of + /// + public static class OutgoingMessageExtensions + { + /// + /// Returns all of the specified type contained in + /// . + /// + public static IEnumerable> Containing(this IEnumerable> repliedMessages) + { + return repliedMessages + .Where(x => x.Message is TMessage) + .Select(x => new RepliedMessage((TMessage) x.Message, x.Options)); + } + + /// + /// Returns all of the specified type contained in + /// . + /// + public static IEnumerable> Containing(this IEnumerable> publishedMessages) + { + return publishedMessages + .Where(x => x.Message is TMessage) + .Select(x => new PublishedMessage((TMessage) x.Message, x.Options)); + } + + /// + /// Returns all of the specified type contained in . + /// + public static IEnumerable> Containing(this IEnumerable> sentMessages) + { + return sentMessages + .Where(x => x.Message is TMessage) + .Select(x => new SentMessage((TMessage) x.Message, x.Options)); + } + + /// + /// Tries to cast the message contained in to . + /// + public static TMessage Message(this RepliedMessage sentMessage) where TMessage : class + { + return sentMessage.Message as TMessage; + } + + /// + /// Tries to cast the message contained in to . + /// + public static TMessage Message(this PublishedMessage sentMessage) where TMessage : class + { + return sentMessage.Message as TMessage; + } + + /// + /// Tries to cast the message contained in to . + /// + public static TMessage Message(this SentMessage sentMessage) where TMessage : class + { + return sentMessage.Message as TMessage; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/PublishedMessage.cs b/src/NServiceBus.Testing.Fakes/PublishedMessage.cs new file mode 100644 index 00000000000..8e065cec25f --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/PublishedMessage.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Testing +{ + /// + /// Represents an outgoing message. Contains the message itself and it's associated options. + /// + /// The message type. + public class PublishedMessage : OutgoingMessage + { + /// + /// Creates a new instance for the given message and options. + /// + public PublishedMessage(TMessage message, PublishOptions options) : base(message, options) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/RepliedMessage.cs b/src/NServiceBus.Testing.Fakes/RepliedMessage.cs new file mode 100644 index 00000000000..58f534522dc --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/RepliedMessage.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Testing +{ + /// + /// Represents an outgoing message. Contains the message itself and it's associated options. + /// + /// The message type. + public class RepliedMessage : OutgoingMessage + { + /// + /// Creates a new instance for the given message and options. + /// + public RepliedMessage(TMessage message, ReplyOptions options) : base(message, options) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/SentMessage.cs b/src/NServiceBus.Testing.Fakes/SentMessage.cs new file mode 100644 index 00000000000..d8f17b734a0 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/SentMessage.cs @@ -0,0 +1,16 @@ +namespace NServiceBus.Testing +{ + /// + /// Represents an outgoing message. Contains the message itself and it's associated options. + /// + /// The message type. + public class SentMessage : OutgoingMessage + { + /// + /// Creates a new instance for the given message and options. + /// + public SentMessage(TMessage message, SendOptions options) : base(message, options) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/Subscription.cs b/src/NServiceBus.Testing.Fakes/Subscription.cs new file mode 100644 index 00000000000..0a0be511fcc --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/Subscription.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Testing +{ + using System; + + /// + /// Represents an event subscription. + /// + public class Subscription : OutgoingMessage + { + /// + /// Creates a new instance for the given event type and it's options. + /// + /// + /// + public Subscription(Type message, SubscribeOptions options) : base(message, options) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableAuditContext.cs b/src/NServiceBus.Testing.Fakes/TestableAuditContext.cs new file mode 100644 index 00000000000..373d7c8339b --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableAuditContext.cs @@ -0,0 +1,39 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using Pipeline; + using Transport; + + /// + /// A testable implementation of . + /// + public partial class TestableAuditContext : TestableBehaviorContext, IAuditContext + { + /// + /// Contains the information added by . + /// + public IDictionary AddedAuditData { get; } = new Dictionary(); + + /// + /// Address of the audit queue. + /// + public string AuditAddress { get; set; } = "audit queue address"; + + /// + /// The message to be audited. + /// + public OutgoingMessage Message { get; set; } = new OutgoingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[0]); + + /// + /// Adds information about the current message that should be audited. + /// + /// The audit key. + /// The value. + public void AddAuditData(string key, string value) + { + AddedAuditData.Add(key, value); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableBatchDispatchContext.cs b/src/NServiceBus.Testing.Fakes/TestableBatchDispatchContext.cs new file mode 100644 index 00000000000..18a79f48808 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableBatchDispatchContext.cs @@ -0,0 +1,21 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using Pipeline; + using Transport; + + /// + /// A testable implementation of . + /// + public partial class TestableBatchDispatchContext : TestableBehaviorContext, IBatchDispatchContext + { + /// + /// The captured transport operations to dispatch. + /// + public IList Operations { get; set; } = new List(); + + IReadOnlyCollection IBatchDispatchContext.Operations => new ReadOnlyCollection(Operations); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableBehaviorContext.cs b/src/NServiceBus.Testing.Fakes/TestableBehaviorContext.cs new file mode 100644 index 00000000000..70c03aa035e --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableBehaviorContext.cs @@ -0,0 +1,35 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using Extensibility; + using ObjectBuilder; + using Pipeline; + + /// + /// A base implementation for contexts implementing . + /// + public abstract partial class TestableBehaviorContext : IBehaviorContext + { + /// + /// A fake implementation. If you want to provide your own implementation + /// override . + /// + public FakeBuilder Builder { get; set; } = new FakeBuilder(); + + /// + /// A which can be used to extend the current object. + /// + public ContextBag Extensions { get; set; } = new ContextBag(); + + IBuilder IBehaviorContext.Builder => GetBuilder(); + + /// + /// Selects the builder returned by . Override this method to provide your custom + /// implementation. + /// + protected virtual IBuilder GetBuilder() + { + return Builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableDispatchContext.cs b/src/NServiceBus.Testing.Fakes/TestableDispatchContext.cs new file mode 100644 index 00000000000..87d86309655 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableDispatchContext.cs @@ -0,0 +1,20 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using Pipeline; + using Transport; + + /// + /// A testable implementation of . + /// + public partial class TestableDispatchContext : TestableBehaviorContext, IDispatchContext + { + /// + /// The operations to be dispatched to the transport. + /// + public IList Operations { get; set; } = new List(); + + IEnumerable IDispatchContext.Operations => Operations; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableEndpointInstance.cs b/src/NServiceBus.Testing.Fakes/TestableEndpointInstance.cs new file mode 100644 index 00000000000..38b6277d091 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableEndpointInstance.cs @@ -0,0 +1,25 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Threading.Tasks; + + /// + /// A testable implementation of . + /// + public partial class TestableEndpointInstance : TestableMessageSession, IEndpointInstance + { + /// + /// Indicates whether has been called or not. + /// + public bool EndpointStopped { get; private set; } + + /// + /// Stops the endpoint. + /// + public virtual Task Stop() + { + EndpointStopped = true; + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableForwardingContext.cs b/src/NServiceBus.Testing.Fakes/TestableForwardingContext.cs new file mode 100644 index 00000000000..76b8f5071c1 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableForwardingContext.cs @@ -0,0 +1,24 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using Pipeline; + using Transport; + + /// + /// A testable implementation for . + /// + public partial class TestableForwardingContext : TestableBehaviorContext, IForwardingContext + { + /// + /// The message to be forwarded. + /// + public OutgoingMessage Message { get; set; } = new OutgoingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[0]); + + /// + /// The address of the forwarding queue. + /// + public string Address { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableIncomingContext.cs b/src/NServiceBus.Testing.Fakes/TestableIncomingContext.cs new file mode 100644 index 00000000000..b9037aef6dd --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableIncomingContext.cs @@ -0,0 +1,36 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using ObjectBuilder; + using Pipeline; + + /// + /// Base implementation for contexts implementing . + /// + public abstract partial class TestableIncomingContext : TestableMessageProcessingContext, IIncomingContext + { + /// + /// Creates a new instance of . + /// + protected TestableIncomingContext(IMessageCreator messageCreator = null) : base(messageCreator) + { + } + + /// + /// A fake implementation. If you want to provide your own implementation + /// override . + /// + public FakeBuilder Builder { get; set; } = new FakeBuilder(); + + IBuilder IBehaviorContext.Builder => GetBuilder(); + + /// + /// Selects the builder returned by . Override this method to provide your custom + /// implementation. + /// + protected virtual IBuilder GetBuilder() + { + return Builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableIncomingLogicalMessageContext.cs b/src/NServiceBus.Testing.Fakes/TestableIncomingLogicalMessageContext.cs new file mode 100644 index 00000000000..6e9535dae84 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableIncomingLogicalMessageContext.cs @@ -0,0 +1,44 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using Pipeline; + using Unicast.Messages; + + /// + /// A testable implementation of . + /// + public partial class TestableIncomingLogicalMessageContext : TestableIncomingContext, IIncomingLogicalMessageContext + { + /// + /// Creates a new instance of . + /// + public TestableIncomingLogicalMessageContext(IMessageCreator messageCreator = null) : base(messageCreator) + { + } + + /// + /// Message being handled. + /// + public LogicalMessage Message { get; set; } = new LogicalMessage(new MessageMetadata(typeof(object)), new object()); + + /// + /// Headers for the incoming message. + /// + public Dictionary Headers { get; set; } = new Dictionary(); + + /// + /// Tells if the message has been handled. + /// + public bool MessageHandled { get; set; } + + /// + /// Updates the message instance contained in . + /// + /// The new instance. + public virtual void UpdateMessageInstance(object newInstance) + { + Message = new LogicalMessage(new MessageMetadata(newInstance.GetType()), newInstance); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableIncomingPhysicalMessageContext.cs b/src/NServiceBus.Testing.Fakes/TestableIncomingPhysicalMessageContext.cs new file mode 100644 index 00000000000..70f0014bddb --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableIncomingPhysicalMessageContext.cs @@ -0,0 +1,36 @@ +// ReSharper disable PartialTypeWithSinglePart + +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using Pipeline; + using Transport; + + /// + /// A testable implementation of . + /// + public partial class TestableIncomingPhysicalMessageContext : TestableIncomingContext, IIncomingPhysicalMessageContext + { + /// + /// Creates a new instance of . + /// + public TestableIncomingPhysicalMessageContext() + { + Message = new IncomingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[] { }); + } + + /// + /// Updates the message with the given body. + /// + public virtual void UpdateMessage(byte[] body) + { + Message = new IncomingMessage(Message.MessageId, Message.Headers, body); + } + + /// + /// The physical message being processed. + /// + public IncomingMessage Message { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableInvokeHandlerContext.cs b/src/NServiceBus.Testing.Fakes/TestableInvokeHandlerContext.cs new file mode 100644 index 00000000000..6b10db08b25 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableInvokeHandlerContext.cs @@ -0,0 +1,84 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Persistence; + using Pipeline; + using Unicast.Messages; + + /// + /// A testable implementation of . + /// + public partial class TestableInvokeHandlerContext : TestableIncomingContext, IInvokeHandlerContext + { + /// + /// Creates a new instance of . + /// + public TestableInvokeHandlerContext(IMessageCreator messageCreator = null) : base(messageCreator) + { + } + + /// + /// Indicates if has been called. + /// + public bool DoNotContinueDispatchingCurrentMessageToHandlersWasCalled { get; set; } + + /// + /// Moves the message being handled to the back of the list of available + /// messages so it can be handled later. + /// + public virtual Task HandleCurrentMessageLater() + { + HandleCurrentMessageLaterWasCalled = true; + return Task.FromResult(0); + } + + /// + /// Indicates if has been called. + /// + public bool HandleCurrentMessageLaterWasCalled { get; set; } + + /// + /// Tells the endpoint to stop dispatching the current message to additional + /// handlers. + /// + public void DoNotContinueDispatchingCurrentMessageToHandlers() + { + DoNotContinueDispatchingCurrentMessageToHandlersWasCalled = true; + } + + /// + /// Gets the synchronized storage session for processing the current message. NServiceBus makes sure the changes made + /// via this session will be persisted before the message receive is acknowledged. + /// + public SynchronizedStorageSession SynchronizedStorageSession { get; set; } + + /// + /// The current being executed. + /// + public MessageHandler MessageHandler { get; set; } = new MessageHandler((instance, message, context) => Task.FromResult(0), typeof(object)); + + /// + /// Message headers. + /// + public Dictionary Headers { get; set; } = new Dictionary(); + + /// + /// The message instance being handled. + /// + public object MessageBeingHandled { get; set; } = new object(); + + /// + /// true if + /// or + /// has been called. + /// + public bool HandlerInvocationAborted => DoNotContinueDispatchingCurrentMessageToHandlersWasCalled || HandleCurrentMessageLaterWasCalled; + + /// + /// Metadata for the incoming message. + /// + public MessageMetadata MessageMetadata { get; set; } = new MessageMetadata(typeof(object)); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableMessageHandlerContext.cs b/src/NServiceBus.Testing.Fakes/TestableMessageHandlerContext.cs new file mode 100644 index 00000000000..e9dfec709b1 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableMessageHandlerContext.cs @@ -0,0 +1,16 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + /// + /// A testable implementation of . + /// + public partial class TestableMessageHandlerContext : TestableInvokeHandlerContext + { + /// + /// Creates a new instance of a . + /// + public TestableMessageHandlerContext(IMessageCreator messageCreator = null) : base(messageCreator) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableMessageProcessingContext.cs b/src/NServiceBus.Testing.Fakes/TestableMessageProcessingContext.cs new file mode 100644 index 00000000000..737da2b17e5 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableMessageProcessingContext.cs @@ -0,0 +1,85 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Threading.Tasks; + + /// + /// Testable implementation of . + /// + public partial class TestableMessageProcessingContext : TestablePipelineContext, IMessageProcessingContext + { + /// + /// Creates a new instance of . + /// + public TestableMessageProcessingContext(IMessageCreator messageCreator = null) : base(messageCreator) + { + } + + /// + /// A list of all messages sent by . + /// + public virtual RepliedMessage[] RepliedMessages => repliedMessages.ToArray(); + + /// + /// A list of all forwarding destinations set by . + /// + public virtual string[] ForwardedMessages => forwardedMessages.ToArray(); + + /// + /// Gets the list of key/value pairs found in the header of the message. + /// + public IDictionary MessageHeaders { get; set; } = new Dictionary(); + + /// + /// Sends the message to the endpoint which sent the message currently being handled. + /// + /// The message to send. + /// Options for this reply. + public virtual Task Reply(object message, ReplyOptions options) + { + repliedMessages.Enqueue(new RepliedMessage(message, options)); + return Task.FromResult(0); + } + + /// + /// Instantiates a message of type T and performs a regular + /// . + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// Options for this reply. + public virtual Task Reply(Action messageConstructor, ReplyOptions options) + { + return Reply(messageCreator.CreateInstance(messageConstructor), options); + } + + /// + /// Forwards the current message being handled to the destination maintaining + /// all of its transport-level properties and headers. + /// + public virtual Task ForwardCurrentMessageTo(string destination) + { + forwardedMessages.Enqueue(destination); + return Task.FromResult(0); + } + + /// + /// The Id of the currently processed message. + /// + public string MessageId { get; set; } = Guid.NewGuid().ToString(); + + /// + /// The address of the endpoint that sent the current message being handled. + /// + public string ReplyToAddress { get; set; } = "reply address"; + + IReadOnlyDictionary IMessageProcessingContext.MessageHeaders => new ReadOnlyDictionary(MessageHeaders); + ConcurrentQueue forwardedMessages = new ConcurrentQueue(); + + ConcurrentQueue> repliedMessages = new ConcurrentQueue>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableMessageSession.cs b/src/NServiceBus.Testing.Fakes/TestableMessageSession.cs new file mode 100644 index 00000000000..3d92e2dfc14 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableMessageSession.cs @@ -0,0 +1,51 @@ +// ReSharper disable PartialTypeWithSinglePart + +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Concurrent; + using System.Threading.Tasks; + + /// + /// A testable implementation. + /// + public partial class TestableMessageSession : TestablePipelineContext, IMessageSession + { + /// + /// A list of all event subscriptions made from this session. + /// + public virtual Subscription[] Subscriptions => subscriptions.ToArray(); + + /// + /// A list of all event subscriptions canceled from this session. + /// + public virtual Unsubscription[] Unsubscription => unsubscriptions.ToArray(); + + /// + /// Subscribes to receive published messages of the specified type. + /// This method is only necessary if you turned off auto-subscribe. + /// + /// The type of event to subscribe to. + /// Options for the subscribe. + public virtual Task Subscribe(Type eventType, SubscribeOptions options) + { + subscriptions.Enqueue(new Subscription(eventType, options)); + return Task.FromResult(0); + } + + /// + /// Unsubscribes to receive published messages of the specified type. + /// + /// The type of event to unsubscribe to. + /// Options for the subscribe. + public virtual Task Unsubscribe(Type eventType, UnsubscribeOptions options) + { + unsubscriptions.Enqueue(new Unsubscription(eventType, options)); + return Task.FromResult(0); + } + + ConcurrentQueue subscriptions = new ConcurrentQueue(); + + ConcurrentQueue unsubscriptions = new ConcurrentQueue(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingContext.cs new file mode 100644 index 00000000000..14c5d74c772 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingContext.cs @@ -0,0 +1,41 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using ObjectBuilder; + using Pipeline; + + /// + /// A base implementation for all behaviors implementing . + /// + public partial class TestableOutgoingContext : TestablePipelineContext, IOutgoingContext + { + /// + /// A fake implementation. If you want to provide your own implementation + /// override . + /// + public FakeBuilder Builder { get; set; } = new FakeBuilder(); + + IBuilder IBehaviorContext.Builder => GetBuilder(); + + /// + /// The id of the outgoing message. + /// + public string MessageId { get; set; } = Guid.NewGuid().ToString(); + + /// + /// The headers of the outgoing message. + /// + public Dictionary Headers { get; set; } = new Dictionary(); + + /// + /// Selects the builder returned by . Override this method to provide your custom + /// implementation. + /// + protected virtual IBuilder GetBuilder() + { + return Builder; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingLogicalMessageContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingLogicalMessageContext.cs new file mode 100644 index 00000000000..27e7d4057f1 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingLogicalMessageContext.cs @@ -0,0 +1,31 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using Pipeline; + using Routing; + + /// + /// A testable implementation of . + /// + public partial class TestableOutgoingLogicalMessageContext : TestableOutgoingContext, IOutgoingLogicalMessageContext + { + /// + /// Updates the message instance. + /// + public virtual void UpdateMessage(object newInstance) + { + Message = new OutgoingLogicalMessage(newInstance.GetType(), newInstance); + } + + /// + /// The outgoing message. + /// + public OutgoingLogicalMessage Message { get; set; } = new OutgoingLogicalMessage(typeof(object), new object()); + + /// + /// The routing strategies for this message. + /// + public IReadOnlyCollection RoutingStrategies { get; set; } = new RoutingStrategy[0]; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingPhysicalMessageContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingPhysicalMessageContext.cs new file mode 100644 index 00000000000..927b8308ebf --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingPhysicalMessageContext.cs @@ -0,0 +1,34 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System.Collections.Generic; + using Pipeline; + using Routing; + + /// + /// A testable implementation of . + /// + public partial class TestableOutgoingPhysicalMessageContext : TestableOutgoingContext, IOutgoingPhysicalMessageContext + { + /// + /// Updates the message with the given body. + /// + public virtual void UpdateMessage(byte[] body) + { + Body = body; + } + + /// + /// The serialized body of the outgoing message. + /// + /// + /// A array containing the serialized contents of the outgoing message. + /// + public byte[] Body { get; set; } = new byte[0]; + + /// + /// The routing strategies for this message. + /// + public IReadOnlyCollection RoutingStrategies { get; set; } = new RoutingStrategy[0]; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingPublishContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingPublishContext.cs new file mode 100644 index 00000000000..968d5cbc9fc --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingPublishContext.cs @@ -0,0 +1,16 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using Pipeline; + + /// + /// A testable implementation of . + /// + public partial class TestableOutgoingPublishContext : TestableOutgoingContext, IOutgoingPublishContext + { + /// + /// The message to be published. + /// + public OutgoingLogicalMessage Message { get; set; } = new OutgoingLogicalMessage(typeof(object), new object()); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingReplyContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingReplyContext.cs new file mode 100644 index 00000000000..63a6ef98ea5 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingReplyContext.cs @@ -0,0 +1,16 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using Pipeline; + + /// + /// A testable implementation of . + /// + public partial class TestableOutgoingReplyContext : TestableOutgoingContext, IOutgoingReplyContext + { + /// + /// The reply message. + /// + public OutgoingLogicalMessage Message { get; set; } = new OutgoingLogicalMessage(typeof(object), new object()); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableOutgoingSendContext.cs b/src/NServiceBus.Testing.Fakes/TestableOutgoingSendContext.cs new file mode 100644 index 00000000000..d292add1089 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableOutgoingSendContext.cs @@ -0,0 +1,16 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using Pipeline; + + /// + /// A testable implementation of . + /// + public partial class TestableOutgoingSendContext : TestableOutgoingContext, IOutgoingSendContext + { + /// + /// The message being sent. + /// + public OutgoingLogicalMessage Message { get; set; } = new OutgoingLogicalMessage(typeof(object), new object()); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestablePipelineContext.cs b/src/NServiceBus.Testing.Fakes/TestablePipelineContext.cs new file mode 100644 index 00000000000..5b8da7cd86e --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestablePipelineContext.cs @@ -0,0 +1,92 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Concurrent; + using System.Threading.Tasks; + using Extensibility; + using MessageInterfaces.MessageMapper.Reflection; + + /// + /// A testable implementation of . + /// + public partial class TestablePipelineContext : IPipelineContext + { + /// + /// Creates a new instance. + /// + public TestablePipelineContext(IMessageCreator messageCreator = null) + { + this.messageCreator = messageCreator ?? new MessageMapper(); + } + + /// + /// A list of all messages sent by . + /// + public virtual SentMessage[] SentMessages => sentMessages.ToArray(); + + /// + /// A list of all messages published by , + /// + public virtual PublishedMessage[] PublishedMessages => publishedMessages.ToArray(); + + /// + /// A which can be used to extend the current object. + /// + public ContextBag Extensions { get; set; } = new ContextBag(); + + /// + /// Sends the provided message. + /// + /// The message to send. + /// The options for the send. + public virtual Task Send(object message, SendOptions options) + { + sentMessages.Enqueue(new SentMessage(message, options)); + + return Task.FromResult(0); + } + + /// + /// Instantiates a message of type T and sends it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// The options for the send. + public virtual Task Send(Action messageConstructor, SendOptions options) + { + return Send(messageCreator.CreateInstance(messageConstructor), options); + } + + /// + /// Publish the message to subscribers. + /// + /// The message to publish. + /// The options for the publish. + public virtual Task Publish(object message, PublishOptions options) + { + publishedMessages.Enqueue(new PublishedMessage(message, options)); + return Task.FromResult(0); + } + + /// + /// Instantiates a message of type T and publishes it. + /// + /// The type of message, usually an interface. + /// An action which initializes properties of the message. + /// Specific options for this event. + public virtual Task Publish(Action messageConstructor, PublishOptions publishOptions) + { + return Publish(messageCreator.CreateInstance(messageConstructor), publishOptions); + } + + /// + /// the instance used to create proxy implementation for message interfaces. + /// + protected IMessageCreator messageCreator; + + ConcurrentQueue> publishedMessages = new ConcurrentQueue>(); + + ConcurrentQueue> sentMessages = new ConcurrentQueue>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableRoutingContext.cs b/src/NServiceBus.Testing.Fakes/TestableRoutingContext.cs new file mode 100644 index 00000000000..3e3779cef88 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableRoutingContext.cs @@ -0,0 +1,25 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using Pipeline; + using Routing; + using Transport; + + /// + /// A testable implementation of . + /// + public partial class TestableRoutingContext : TestableBehaviorContext, IRoutingContext + { + /// + /// The message to dispatch the the transport. + /// + public OutgoingMessage Message { get; set; } = new OutgoingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[0]); + + /// + /// The routing strategies for the operation to be dispatched. + /// + public IReadOnlyCollection RoutingStrategies { get; set; } = new RoutingStrategy[0]; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableSubscribeContext.cs b/src/NServiceBus.Testing.Fakes/TestableSubscribeContext.cs new file mode 100644 index 00000000000..2199a7be7f1 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableSubscribeContext.cs @@ -0,0 +1,17 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using Pipeline; + + /// + /// A testable implementation of . + /// + public partial class TestableSubscribeContext : TestableBehaviorContext, ISubscribeContext + { + /// + /// The type of the event. + /// + public Type EventType { get; set; } = typeof(object); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableTransportReceiveContext.cs b/src/NServiceBus.Testing.Fakes/TestableTransportReceiveContext.cs new file mode 100644 index 00000000000..d24b53aa177 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableTransportReceiveContext.cs @@ -0,0 +1,33 @@ +// ReSharper disable PartialTypeWithSinglePart + +namespace NServiceBus.Testing +{ + using System; + using System.Collections.Generic; + using Pipeline; + using Transport; + + /// + /// A testable implementation for . + /// + public partial class TestableTransportReceiveContext : TestableBehaviorContext, ITransportReceiveContext + { + /// + /// Indicated whether has been called or not. + /// + public bool ReceiveOperationAborted { get; set; } + + /// + /// Allows the pipeline to flag that it has been aborted and the receive operation should be rolled back. + /// + public virtual void AbortReceiveOperation() + { + ReceiveOperationAborted = true; + } + + /// + /// The physical message being processed. + /// + public IncomingMessage Message { get; set; } = new IncomingMessage(Guid.NewGuid().ToString(), new Dictionary(), new byte[0]); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestableUnsubscribeContext.cs b/src/NServiceBus.Testing.Fakes/TestableUnsubscribeContext.cs new file mode 100644 index 00000000000..00963496ccc --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestableUnsubscribeContext.cs @@ -0,0 +1,17 @@ +// ReSharper disable PartialTypeWithSinglePart +namespace NServiceBus.Testing +{ + using System; + using Pipeline; + + /// + /// A testable implementation of . + /// + public partial class TestableUnsubscribeContext : TestableBehaviorContext, IUnsubscribeContext + { + /// + /// The type of the event. + /// + public Type EventType { get; set; } = typeof(object); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TestingLoggerFactory.cs b/src/NServiceBus.Testing.Fakes/TestingLoggerFactory.cs new file mode 100644 index 00000000000..d5cfb4d6953 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TestingLoggerFactory.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.Testing +{ + using System; + using System.IO; + using Logging; + + /// + /// Logger factory which allows to log to a text writer. + /// + public class TestingLoggerFactory : LoggingFactoryDefinition + { + /// + /// Creates a new instance of a testing logger factory. + /// + public TestingLoggerFactory() + { + level = new Lazy(() => LogLevel.Debug); + writer = new Lazy(() => TextWriter.Null); + } + + /// + /// Controls the . + /// + public void Level(LogLevel level) + { + this.level = new Lazy(() => level); + } + + /// + /// Instructs the logger to write to the provided text writer. + /// + /// The text writer to be used. + public void WriteTo(TextWriter writer) + { + this.writer = new Lazy(() => writer); + } + + /// + /// Constructs an instance of for use by . + /// + protected override ILoggerFactory GetLoggingFactory() + { + var loggerFactory = new DefaultTestingLoggerFactory(level.Value, writer.Value); + var message = $"Logging to testing logger with level {level}"; + loggerFactory.Write(GetType().Name, LogLevel.Info, message); + return loggerFactory; + } + + Lazy level; + Lazy writer; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/TextWriterLogger.cs b/src/NServiceBus.Testing.Fakes/TextWriterLogger.cs new file mode 100644 index 00000000000..be5d96bf5f5 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/TextWriterLogger.cs @@ -0,0 +1,19 @@ +namespace NServiceBus.Testing +{ + using System.IO; + + class TextWriterLogger + { + public TextWriterLogger(TextWriter textWriter) + { + writer = textWriter; + } + + public void Write(string message) + { + writer.WriteLine(message); + } + + TextWriter writer; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/Unsubscription.cs b/src/NServiceBus.Testing.Fakes/Unsubscription.cs new file mode 100644 index 00000000000..957bd337549 --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/Unsubscription.cs @@ -0,0 +1,17 @@ +namespace NServiceBus.Testing +{ + using System; + + /// + /// Represents an event subscription cancellation. + /// + public class Unsubscription : OutgoingMessage + { + /// + /// Creates a new instance for the given event type and it's options. + /// + public Unsubscription(Type message, UnsubscribeOptions options) : base(message, options) + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Testing.Fakes/packages.config b/src/NServiceBus.Testing.Fakes/packages.config new file mode 100644 index 00000000000..bfa70365ccf --- /dev/null +++ b/src/NServiceBus.Testing.Fakes/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/ConfigureMsmqTransportInfrastructure.cs b/src/NServiceBus.TransportTests/ConfigureMsmqTransportInfrastructure.cs new file mode 100644 index 00000000000..99e0d7ef664 --- /dev/null +++ b/src/NServiceBus.TransportTests/ConfigureMsmqTransportInfrastructure.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Messaging; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.Settings; +using NServiceBus.Transport; +using NServiceBus.TransportTests; + +class ConfigureMsmqTransportInfrastructure : IConfigureTransportInfrastructure +{ + public TransportConfigurationResult Configure(SettingsHolder settings, TransportTransactionMode transactionMode) + { + var msmqTransportDefinition = new MsmqTransport(); + settingsHolder = settings; + return new TransportConfigurationResult + { + TransportInfrastructure = msmqTransportDefinition.Initialize(settingsHolder, ""), + PurgeInputQueueOnStartup = true + }; + } + + public Task Cleanup() + { + var queueBindings = settingsHolder.Get(); + var allQueues = MessageQueue.GetPrivateQueuesByMachine("localhost"); + var queuesToBeDeleted = new List(); + + foreach (var messageQueue in allQueues) + { + using (messageQueue) + { + if (queueBindings.ReceivingAddresses.Any(ra => + { + var indexOfAt = ra.IndexOf("@", StringComparison.Ordinal); + if (indexOfAt >= 0) + { + ra = ra.Substring(0, indexOfAt); + } + return messageQueue.QueueName.StartsWith(@"private$\" + ra, StringComparison.OrdinalIgnoreCase); + })) + { + queuesToBeDeleted.Add(messageQueue.Path); + } + } + } + + foreach (var queuePath in queuesToBeDeleted) + { + try + { + MessageQueue.Delete(queuePath); + Console.WriteLine("Deleted '{0}' queue", queuePath); + } + catch (Exception) + { + Console.WriteLine("Could not delete queue '{0}'", queuePath); + } + } + + MessageQueue.ClearConnectionCache(); + + return Task.FromResult(0); + } + + SettingsHolder settingsHolder; +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/IConfigureTransportInfrastructure.cs b/src/NServiceBus.TransportTests/IConfigureTransportInfrastructure.cs new file mode 100644 index 00000000000..283bc9124e1 --- /dev/null +++ b/src/NServiceBus.TransportTests/IConfigureTransportInfrastructure.cs @@ -0,0 +1,27 @@ +namespace NServiceBus.TransportTests +{ + using System.Threading.Tasks; + using Settings; + + /// + /// Provide a mechanism in components tests for transports + /// to configure a transport infrastructure for a test and then clean up afterwards. + /// + public interface IConfigureTransportInfrastructure + { + /// + /// Gives the transport a chance to configure before the test starts. + /// + /// The settings to be passed into the infrastructure. + /// Transaction mode for which transport seam should be configured. + /// Transport configuration result + TransportConfigurationResult Configure(SettingsHolder settings, TransportTransactionMode transactionMode); + + /// + /// Gives the transport chance to clean up after the test is complete. Implementations of this class may store + /// private variables during Configure to use during the cleanup phase. + /// + /// An async Task. + Task Cleanup(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/NServiceBus.TransportTests.csproj b/src/NServiceBus.TransportTests/NServiceBus.TransportTests.csproj new file mode 100644 index 00000000000..4f106883d54 --- /dev/null +++ b/src/NServiceBus.TransportTests/NServiceBus.TransportTests.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {7EF57153-179F-449A-9C81-BD049B1B5613} + Library + Properties + NServiceBus.TransportTests + NServiceBus.TransportTests + v4.5.2 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {dd48b2d0-e996-412d-9157-821ed8b17a9d} + NServiceBus.Core + + + + + nservicebus.transporttests.nuspec + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/NServiceBusTransportTest.cs b/src/NServiceBus.TransportTests/NServiceBusTransportTest.cs new file mode 100644 index 00000000000..7e1ff12a857 --- /dev/null +++ b/src/NServiceBus.TransportTests/NServiceBusTransportTest.cs @@ -0,0 +1,268 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Security.Principal; + using System.Threading; + using System.Threading.Tasks; + using DeliveryConstraints; + using Extensibility; + using NUnit.Framework; + using Routing; + using Settings; + using Transport; + + public abstract class NServiceBusTransportTest + { + [SetUp] + public void SetUp() + { + testId = Guid.NewGuid().ToString(); + } + + static IConfigureTransportInfrastructure CreateConfigurer() + { + var transport = EnvironmentHelper.GetEnvironmentVariable("Transport.UseSpecific"); + + if (string.IsNullOrWhiteSpace(transport)) + { + transport = transportDefinitions.Value.FirstOrDefault(t => t.Name != MsmqDescriptorKey)?.Name ?? MsmqDescriptorKey; + } + + var typeName = $"Configure{transport}Infrastructure"; + + var configurerType = Type.GetType(typeName, false); + + if (configurerType == null) + { + throw new InvalidOperationException($"Transport Test project must include a non-namespaced class named '{typeName}' implementing {typeof(IConfigureTransportInfrastructure).Name}."); + } + + var configurer = Activator.CreateInstance(configurerType) as IConfigureTransportInfrastructure; + + if (configurer == null) + { + throw new InvalidOperationException($"{typeName} does not implement {typeof(IConfigureTransportInfrastructure).Name}."); + } + + return configurer; + } + + [TearDown] + public void TearDown() + { + testCancellationTokenSource?.Dispose(); + MessagePump?.Stop().GetAwaiter().GetResult(); + Configurer?.Cleanup().GetAwaiter().GetResult(); + + transportSettings.Clear(); + } + + protected async Task StartPump(Func onMessage, Func> onError, TransportTransactionMode transactionMode, Action onCriticalError = null) + { + InputQueueName = GetTestName() + transactionMode; + ErrorQueueName = $"{InputQueueName}.error"; + + transportSettings.Set("NServiceBus.Routing.EndpointName", InputQueueName); + + var queueBindings = new QueueBindings(); + queueBindings.BindReceiving(InputQueueName); + queueBindings.BindSending(ErrorQueueName); + transportSettings.Set(queueBindings); + + transportSettings.Set(new EndpointInstances()); + + Configurer = CreateConfigurer(); + + var configuration = Configurer.Configure(transportSettings, transactionMode); + + TransportInfrastructure = configuration.TransportInfrastructure; + + IgnoreUnsupportedTransactionModes(transactionMode); + IgnoreUnsupportedDeliveryConstraints(); + + ReceiveInfrastructure = TransportInfrastructure.ConfigureReceiveInfrastructure(); + SendInfrastructure = TransportInfrastructure.ConfigureSendInfrastructure(); + + lazyDispatcher = new Lazy(() => SendInfrastructure.DispatcherFactory()); + + MessagePump = ReceiveInfrastructure.MessagePumpFactory(); + + var queueCreator = ReceiveInfrastructure.QueueCreatorFactory(); + await queueCreator.CreateQueueIfNecessary(queueBindings, WindowsIdentity.GetCurrent().Name); + + var pushSettings = new PushSettings(InputQueueName, ErrorQueueName, configuration.PurgeInputQueueOnStartup, transactionMode); + await MessagePump.Init( + context => + { + if (context.Headers.ContainsKey(TestIdHeaderName) && + context.Headers[TestIdHeaderName] == testId) + { + return onMessage(context); + } + + return Task.FromResult(0); + }, + context => + { + if (context.Message.Headers.ContainsKey(TestIdHeaderName) && + context.Message.Headers[TestIdHeaderName] == testId) + { + return onError(context); + } + + return Task.FromResult(ErrorHandleResult.Handled); + }, + new FakeCriticalError(onCriticalError), + pushSettings); + + MessagePump.Start(PushRuntimeSettings.Default); + } + + void IgnoreUnsupportedDeliveryConstraints() + { + var supportedDeliveryConstraints = TransportInfrastructure.DeliveryConstraints.ToList(); + var unsupportedDeliveryConstraints = requiredDeliveryConstraints.Where(required => !supportedDeliveryConstraints.Contains(required)) + .ToList(); + + if (unsupportedDeliveryConstraints.Any()) + { + var unsupported = string.Join(",", unsupportedDeliveryConstraints.Select(c => c.Name)); + Assert.Ignore($"Transport doesn't support required delivery constraint(s) {unsupported}"); + } + } + + void IgnoreUnsupportedTransactionModes(TransportTransactionMode requestedTransactionMode) + { + if (TransportInfrastructure.TransactionMode < requestedTransactionMode) + { + Assert.Ignore($"Only relevant for transports supporting {requestedTransactionMode} or higher"); + } + } + + protected Task SendMessage(string address, + Dictionary headers = null, + TransportTransaction transportTransaction = null, + List deliveryConstraints = null) + { + var messageId = Guid.NewGuid().ToString(); + var message = new OutgoingMessage(messageId, headers ?? new Dictionary(), new byte[0]); + + if (message.Headers.ContainsKey(TestIdHeaderName) == false) + { + message.Headers.Add(TestIdHeaderName, testId); + } + + var dispatcher = lazyDispatcher.Value; + + if (transportTransaction == null) + { + transportTransaction = new TransportTransaction(); + } + var transportOperation = new TransportOperation(message, new UnicastAddressTag(address), DispatchConsistency.Default, deliveryConstraints ?? new List()); + + return dispatcher.Dispatch(new TransportOperations(transportOperation), transportTransaction, new ContextBag()); + } + + protected void OnTestTimeout(Action onTimeoutAction) + { + testCancellationTokenSource = new CancellationTokenSource(); + + testCancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30)); + testCancellationTokenSource.Token.Register(onTimeoutAction); + } + + protected virtual TransportInfrastructure CreateTransportInfrastructure() + { + var msmqTransportDefinition = new MsmqTransport(); + return msmqTransportDefinition.Initialize(new SettingsHolder(), ""); + } + + protected void RequireDeliveryConstraint() where T : DeliveryConstraint + { + requiredDeliveryConstraints.Add(typeof(T)); + } + + static string GetTestName() + { + var index = 1; + var frame = new StackFrame(index); + Type type; + + while (true) + { + type = frame.GetMethod().DeclaringType; + if (type != null && !type.IsAbstract && typeof(NServiceBusTransportTest).IsAssignableFrom(type)) + { + break; + } + + frame = new StackFrame(++index); + } + + var classCallingUs = type.FullName.Split('.').Last(); + + var testName = classCallingUs.Split('+').First(); + + testName = testName.Replace("When_", ""); + + testName = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(testName); + + testName = testName.Replace("_", ""); + + return testName; + } + + protected string InputQueueName; + protected string ErrorQueueName; + + string testId; + + List requiredDeliveryConstraints = new List(); + SettingsHolder transportSettings = new SettingsHolder(); + Lazy lazyDispatcher; + TransportReceiveInfrastructure ReceiveInfrastructure; + TransportSendInfrastructure SendInfrastructure; + TransportInfrastructure TransportInfrastructure; + IPushMessages MessagePump; + CancellationTokenSource testCancellationTokenSource; + IConfigureTransportInfrastructure Configurer; + + static string MsmqDescriptorKey = "MsmqTransport"; + static string TestIdHeaderName = "TransportTest.TestId"; + + static Lazy> transportDefinitions = new Lazy>(() => TypeScanner.GetAllTypesAssignableTo().ToList()); + + class FakeCriticalError : CriticalError + { + public FakeCriticalError(Action errorAction) : base(null) + { + this.errorAction = errorAction ?? ((s, e) => { }); + } + + public override void Raise(string errorMessage, Exception exception) + { + errorAction(errorMessage, exception); + } + + Action errorAction; + } + + class EnvironmentHelper + { + public static string GetEnvironmentVariable(string variable) + { + var candidate = Environment.GetEnvironmentVariable(variable, EnvironmentVariableTarget.User); + + if (string.IsNullOrWhiteSpace(candidate)) + { + return Environment.GetEnvironmentVariable(variable); + } + + return candidate; + } + } + } +} diff --git a/src/NServiceBus.TransportTests/TransportConfigurationResult.cs b/src/NServiceBus.TransportTests/TransportConfigurationResult.cs new file mode 100644 index 00000000000..09640f95c18 --- /dev/null +++ b/src/NServiceBus.TransportTests/TransportConfigurationResult.cs @@ -0,0 +1,30 @@ +namespace NServiceBus.TransportTests +{ + using Transport; + + /// + /// Transport configuration result returned by . + /// + public class TransportConfigurationResult + { + + /// + /// Transport infrastructure. + /// + public TransportInfrastructure TransportInfrastructure { get; set; } + + /// + /// Flag representing if input queue should be purged before running any test. + /// + public bool PurgeInputQueueOnStartup { get; set; } + + + /// + /// Creates . + /// + public TransportConfigurationResult() + { + PurgeInputQueueOnStartup = true; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/TypeScanner.cs b/src/NServiceBus.TransportTests/TypeScanner.cs new file mode 100644 index 00000000000..6d88367c518 --- /dev/null +++ b/src/NServiceBus.TransportTests/TypeScanner.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Hosting.Helpers; + + public class TypeScanner + { + static IEnumerable AvailableAssemblies + { + get + { + if (assemblies == null) + { + var result = new AssemblyScanner().GetScannableAssemblies(); + + assemblies = result.Assemblies.Where(a => + { + var references = a.GetReferencedAssemblies(); + + return references.All(an => an.Name != "nunit.framework"); + }).ToList(); + } + + return assemblies; + } + } + + public static IEnumerable GetAllTypesAssignableTo() + { + return AvailableAssemblies.SelectMany(a => a.GetTypes()) + .Where(t => typeof(T).IsAssignableFrom(t) && t != typeof(T)) + .ToList(); + } + + static List assemblies; + } +} diff --git a/src/NServiceBus.TransportTests/When_failure_happens_after_send.cs b/src/NServiceBus.TransportTests/When_failure_happens_after_send.cs new file mode 100644 index 00000000000..f9410496bec --- /dev/null +++ b/src/NServiceBus.TransportTests/When_failure_happens_after_send.cs @@ -0,0 +1,52 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_failure_happens_after_send : NServiceBusTransportTest + { + //[TestCase(TransportTransactionMode.None)] - not relevant + //[TestCase(TransportTransactionMode.ReceiveOnly)] - not relevant + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_not_emit_messages(TransportTransactionMode transactionMode) + { + var onMessageCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onMessageCalled.SetCanceled()); + + await StartPump(async context => + { + if (context.Headers.ContainsKey("CompleteTest")) + { + onMessageCalled.SetResult(true); + return; + } + + if (context.Headers.ContainsKey("EnlistedSend")) + { + onMessageCalled.SetResult(false); + return; + } + + await SendMessage(InputQueueName, new Dictionary { { "EnlistedSend", "true" } }, context.TransportTransaction); + + throw new Exception("Simulated exception"); + + }, + async context => + { + await SendMessage(InputQueueName, new Dictionary { { "CompleteTest", "true" } }, context.TransportTransaction); + + return ErrorHandleResult.Handled; + }, transactionMode); + + await SendMessage(InputQueueName, new Dictionary { { "MyHeader", "MyValue" } }); + + Assert.True(await onMessageCalled.Task, "Should not emit enlisted sends"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_message_is_available.cs b/src/NServiceBus.TransportTests/When_message_is_available.cs new file mode 100644 index 00000000000..1e0056ca600 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_message_is_available.cs @@ -0,0 +1,43 @@ +namespace NServiceBus.TransportTests +{ + using System.Collections.Generic; + using System.Text; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_message_is_available : NServiceBusTransportTest + { + [TestCase(TransportTransactionMode.None)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_invoke_on_message(TransportTransactionMode transactionMode) + { + var onMessageCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onMessageCalled.SetCanceled()); + + await StartPump(context => + { + var body = Encoding.UTF8.GetString(context.Body); + + Assert.AreEqual("", body, "Should pass the body"); + + onMessageCalled.SetResult(context); + return Task.FromResult(0); + }, + context => Task.FromResult(ErrorHandleResult.Handled), transactionMode); + + await SendMessage(InputQueueName, new Dictionary + { + {"MyHeader", "MyValue"} + }); + + var messageContext = await onMessageCalled.Task; + + Assert.False(string.IsNullOrEmpty(messageContext.MessageId), "Should pass the native message id"); + Assert.AreEqual("MyValue", messageContext.Headers["MyHeader"], "Should pass the message headers"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_on_error_throws.cs b/src/NServiceBus.TransportTests/When_on_error_throws.cs new file mode 100644 index 00000000000..dfee02831bd --- /dev/null +++ b/src/NServiceBus.TransportTests/When_on_error_throws.cs @@ -0,0 +1,48 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_on_error_throws : NServiceBusTransportTest + { + // [TestCase(TransportTransactionMode.None)] -- not relevant + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_reinvoke_on_error_with_original_exception(TransportTransactionMode transactionMode) + { + var onErrorCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onErrorCalled.SetCanceled()); + + var firstInvocation = true; + + await StartPump( + context => + { + throw new Exception("Simulated exception"); + }, + context => + { + if (firstInvocation) + { + firstInvocation = false; + + throw new Exception("Exception from onError"); + } + + onErrorCalled.SetResult(context); + + return Task.FromResult(ErrorHandleResult.Handled); + }, transactionMode); + + await SendMessage(InputQueueName); + + var errorContext = await onErrorCalled.Task; + + Assert.AreEqual(errorContext.Exception.Message, "Simulated exception"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_on_message_throws.cs b/src/NServiceBus.TransportTests/When_on_message_throws.cs new file mode 100644 index 00000000000..e6be8a3da69 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_on_message_throws.cs @@ -0,0 +1,41 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_on_message_throws : NServiceBusTransportTest + { + [TestCase(TransportTransactionMode.None)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_call_on_error(TransportTransactionMode transactionMode) + { + var onErrorCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onErrorCalled.SetCanceled()); + + await StartPump(context => + { + throw new Exception("Simulated exception"); + }, + context => + { + onErrorCalled.SetResult(context); + + return Task.FromResult(ErrorHandleResult.Handled); + }, transactionMode); + + await SendMessage(InputQueueName, new Dictionary { { "MyHeader", "MyValue" } }); + + var errorContext = await onErrorCalled.Task; + + Assert.AreEqual(errorContext.Exception.Message, "Simulated exception", "Should preserve the exception"); + Assert.AreEqual(1, errorContext.ImmediateProcessingFailures, "Should track the number of delivery attempts"); + Assert.AreEqual("MyValue", errorContext.Message.Headers["MyHeader"], "Should pass the message headers"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_on_message_throws_after_delayed_retry.cs b/src/NServiceBus.TransportTests/When_on_message_throws_after_delayed_retry.cs new file mode 100644 index 00000000000..30efdab9687 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_on_message_throws_after_delayed_retry.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_on_message_throws_after_delayed_retry : NServiceBusTransportTest + { + //[TestCase(TransportTransactionMode.None)] - not relevant + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_reset_delivery_counter(TransportTransactionMode transactionMode) + { + var onErrorInvoked = new TaskCompletionSource(); + + OnTestTimeout(() => onErrorInvoked.SetCanceled()); + + var numberOfOnErrorInvocations = 0; + + await StartPump( + context => + { + throw new Exception("Simulated exception"); + }, + async context => + { + numberOfOnErrorInvocations += 1; + + if (numberOfOnErrorInvocations == 1) + { + await SendMessage(InputQueueName, context.Message.Headers, context.TransportTransaction); + } + else + { + onErrorInvoked.SetResult(context); + } + + return ErrorHandleResult.Handled; + }, transactionMode); + + await SendMessage(InputQueueName, new Dictionary { { "MyHeader", "MyValue" } }); + + var errorContext = await onErrorInvoked.Task; + + Assert.AreEqual(1, errorContext.ImmediateProcessingFailures, "Should track delivery attempts between immediate retries"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_on_message_throws_after_immediate_retry.cs b/src/NServiceBus.TransportTests/When_on_message_throws_after_immediate_retry.cs new file mode 100644 index 00000000000..a96611c55f2 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_on_message_throws_after_immediate_retry.cs @@ -0,0 +1,49 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_on_message_throws_after_immediate_retry : NServiceBusTransportTest + { + //[TestCase(TransportTransactionMode.None)] - not relevant + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_persist_message_delivery_count_between_on_error_calls(TransportTransactionMode transactionMode) + { + var onErrorInvoked = new TaskCompletionSource(); + + OnTestTimeout(() => onErrorInvoked.SetCanceled()); + + var numberOfOnErrorInvocations = 0; + + await StartPump( + context => + { + throw new Exception("Simulated exception"); + }, + context => + { + numberOfOnErrorInvocations += 1; + + if (numberOfOnErrorInvocations == 3) + { + onErrorInvoked.SetResult(context); + + return Task.FromResult(ErrorHandleResult.Handled); + } + + return Task.FromResult(ErrorHandleResult.RetryRequired); + }, transactionMode); + + await SendMessage(InputQueueName, new Dictionary { { "MyHeader", "MyValue" } }); + + var errorContext = await onErrorInvoked.Task; + + Assert.AreEqual(numberOfOnErrorInvocations, errorContext.ImmediateProcessingFailures, "Should track delivery attempts between immediate retries"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_requesting_immediate_retry.cs b/src/NServiceBus.TransportTests/When_requesting_immediate_retry.cs new file mode 100644 index 00000000000..6a2f69f69a0 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_requesting_immediate_retry.cs @@ -0,0 +1,40 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_requesting_immediate_retry : NServiceBusTransportTest + { + //[TestCase(TransportTransactionMode.None)] - Currently not supported, but there are plans to change that: https://github.com/Particular/NServiceBus/issues/2750 + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_retry_immediately(TransportTransactionMode transactionMode) + { + var messageRetried = new TaskCompletionSource(); + + OnTestTimeout(() => messageRetried.SetResult(false)); + + var hasBeenCalled = false; + + await StartPump( + context => + { + if (hasBeenCalled) + { + messageRetried.SetResult(true); + return Task.FromResult(0); + } + hasBeenCalled = true; + throw new Exception("Simulated exception"); + }, + context => Task.FromResult(ErrorHandleResult.RetryRequired), transactionMode); + + await SendMessage(InputQueueName); + + Assert.True(await messageRetried.Task, "Should retry if asked so"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_scope_dispose_throws.cs b/src/NServiceBus.TransportTests/When_scope_dispose_throws.cs new file mode 100644 index 00000000000..23736b4bd3b --- /dev/null +++ b/src/NServiceBus.TransportTests/When_scope_dispose_throws.cs @@ -0,0 +1,69 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Threading.Tasks; + using System.Transactions; + using NUnit.Framework; + using Transport; + + public class When_scope_dispose_throws : NServiceBusTransportTest + { + [Test] + public async Task Should_call_on_error() + { + var onErrorCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onErrorCalled.SetResult(null)); + + await StartPump(context => + { + // handler enlists a failing transaction enlistment to the DTC transaction which will fail when commiting the transaction. + Transaction.Current.EnlistDurable(EnlistmentWhichFailesDuringPrepare.Id, new EnlistmentWhichFailesDuringPrepare(), EnlistmentOptions.None); + + return Task.FromResult(0); + }, + context => + { + onErrorCalled.SetResult(context); + return Task.FromResult(ErrorHandleResult.Handled); + } + ,TransportTransactionMode.TransactionScope); + + await SendMessage(InputQueueName); + + var errorContext = await onErrorCalled.Task; + + Assert.IsInstanceOf(errorContext.Exception); + + // since some transports doesn't have native retry counters we can't expect the attempts to be fully consistent since if + // dispose throws the message might be picked up before the counter is incremented + Assert.LessOrEqual(1, errorContext.ImmediateProcessingFailures); + } + } + + class EnlistmentWhichFailesDuringPrepare : IEnlistmentNotification + { + public static readonly Guid Id = Guid.NewGuid(); + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + // fail during prepare, this will cause scope.Dispose to throw + preparingEnlistment.ForceRollback(); + } + + public void Commit(Enlistment enlistment) + { + enlistment.Done(); + } + + public void Rollback(Enlistment enlistment) + { + enlistment.Done(); + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_sending_from_on_error.cs b/src/NServiceBus.TransportTests/When_sending_from_on_error.cs new file mode 100644 index 00000000000..574ad34cc77 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_sending_from_on_error.cs @@ -0,0 +1,44 @@ +namespace NServiceBus.TransportTests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_sending_from_on_error : NServiceBusTransportTest + { + [TestCase(TransportTransactionMode.None)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_dispatch_the_message(TransportTransactionMode transactionMode) + { + var messageReceived = new TaskCompletionSource(); + + OnTestTimeout(() => messageReceived.SetResult(false)); + + await StartPump( + context => + { + if (context.Headers.ContainsKey("FromOnError")) + { + messageReceived.SetResult(true); + return Task.FromResult(0); + } + + throw new Exception("Simulated exception"); + }, + async context => + { + await SendMessage(InputQueueName, new Dictionary { { "FromOnError", "true" } }, context.TransportTransaction); + + return ErrorHandleResult.Handled; + }, transactionMode); + + await SendMessage(InputQueueName); + + Assert.True(await messageReceived.Task, "Message not dispatched properly"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_user_aborts_processing.cs b/src/NServiceBus.TransportTests/When_user_aborts_processing.cs new file mode 100644 index 00000000000..3890200f528 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_user_aborts_processing.cs @@ -0,0 +1,47 @@ +namespace NServiceBus.TransportTests +{ + using System.Threading.Tasks; + using NUnit.Framework; + using Transport; + + public class When_user_aborts_processing : NServiceBusTransportTest + { + //[TestCase(TransportTransactionMode.None)] - Not currently supported since we can't rollback the message. We could consider inmemory retry similar to https://github.com/Particular/NServiceBus/issues/2750 in the future + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_retry_immediately(TransportTransactionMode transactionMode) + { + var messageRedelivered = new TaskCompletionSource(); + + OnTestTimeout(() => messageRedelivered.SetResult(false)); + + var hasBeenCalled = false; + var onErrorCalled = false; + + await StartPump( + context => + { + if (hasBeenCalled) + { + messageRedelivered.SetResult(true); + return Task.FromResult(0); + } + hasBeenCalled = true; + context.ReceiveCancellationTokenSource.Cancel(); + + return Task.FromResult(0); + }, + context => + { + onErrorCalled = true; + return Task.FromResult(ErrorHandleResult.RetryRequired); + }, transactionMode); + + await SendMessage(InputQueueName); + + Assert.True(await messageRedelivered.Task, "Should redeliver message"); + Assert.False(onErrorCalled, "Abort should not invoke on error"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/When_using_non_durable_delivery.cs b/src/NServiceBus.TransportTests/When_using_non_durable_delivery.cs new file mode 100644 index 00000000000..382a0820ae5 --- /dev/null +++ b/src/NServiceBus.TransportTests/When_using_non_durable_delivery.cs @@ -0,0 +1,37 @@ +namespace NServiceBus.TransportTests +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using DeliveryConstraints; + using NUnit.Framework; + using Transport; + + public class When_using_non_durable_delivery : NServiceBusTransportTest + { + [TestCase(TransportTransactionMode.None)] + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + [TestCase(TransportTransactionMode.TransactionScope)] + public async Task Should_invoke_on_message(TransportTransactionMode transactionMode) + { + RequireDeliveryConstraint(); + + var onMessageCalled = new TaskCompletionSource(); + + OnTestTimeout(() => onMessageCalled.SetCanceled()); + + await StartPump(context => + { + onMessageCalled.SetResult(context); + return Task.FromResult(0); + }, + context => Task.FromResult(ErrorHandleResult.Handled), transactionMode); + + await SendMessage(InputQueueName, deliveryConstraints: new List { new NonDurableDelivery() }); + + var messageContext = await onMessageCalled.Task; + + Assert.NotNull(messageContext); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.TransportTests/packages.config b/src/NServiceBus.TransportTests/packages.config new file mode 100644 index 00000000000..d7eadaa2952 --- /dev/null +++ b/src/NServiceBus.TransportTests/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/NServiceBus.sln b/src/NServiceBus.sln index e7c3d8ba198..01128efb32d 100644 --- a/src/NServiceBus.sln +++ b/src/NServiceBus.sln @@ -1,19 +1,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.ContainerTests", "NServiceBus.ContainerTests\NServiceBus.ContainerTests.csproj", "{0A282BF4-0957-4074-8D5E-C2FB8634A3AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Serializers.XML.XsdGenerator", "NServiceBus.Serializers.XML.XsdGenerator\NServiceBus.Serializers.XML.XsdGenerator.csproj", "{85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2904B75F-8F07-4C07-BABC-BC42533B3E75}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{EF5E77D7-74B1-422D-B34E-AED35E19249E}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Core", "NServiceBus.Core\NServiceBus.Core.csproj", "{DD48B2D0-E996-412D-9157-821ED8B17A9D}" - ProjectSection(ProjectDependencies) = postProject - {5E51EFBF-329F-4D3A-B86E-CC111697746F} = {5E51EFBF-329F-4D3A-B86E-CC111697746F} - EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Core.Tests", "NServiceBus.Core.Tests\NServiceBus.Core.Tests.csproj", "{2C8F181B-9BAF-4858-968B-1C16F5DDCFA7}" EndProject @@ -21,15 +14,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.AcceptanceTesti EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.AcceptanceTests", "NServiceBus.AcceptanceTests\NServiceBus.AcceptanceTests.csproj", "{6A9E04E7-6229-4A3E-B94A-DA168E962B5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReturnToSourceQueue", "ReturnToSourceQueue\ReturnToSourceQueue.csproj", "{C4693C58-BB66-4630-984E-313E1BCC2474}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Core.x86.Tests", "NServiceBus.Core.Tests.x86\NServiceBus.Core.x86.Tests.csproj", "{389628F6-2FB4-4AFF-BACC-16726C44FFA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.PerformanceTests", "NServiceBus.PerformanceTests\NServiceBus.PerformanceTests.csproj", "{2F2FBB64-FE67-4204-8E27-E1DB39ED843D}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{ACCDE2E1-3F56-47BA-8CFF-199931BAC6B0}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Msmq.AcceptanceTests", "NServiceBus.Msmq.AcceptanceTests\NServiceBus.Msmq.AcceptanceTests.csproj", "{B83EB057-56C7-4296-B0C7-E41F4F70CB02}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.PowerShell.Development", "NServiceBus.PowerShell.Development\NServiceBus.PowerShell.Development.csproj", "{5E51EFBF-329F-4D3A-B86E-CC111697746F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.Testing.Fakes", "NServiceBus.Testing.Fakes\NServiceBus.Testing.Fakes.csproj", "{80105366-8EF9-494D-A296-E100E82224CF}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{8B8CDBEC-862C-4A0A-83FF-90FB4CAEAB39}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.TransportTests", "NServiceBus.TransportTests\NServiceBus.TransportTests.csproj", "{7EF57153-179F-449A-9C81-BD049B1B5613}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,16 +47,6 @@ Global {0A282BF4-0957-4074-8D5E-C2FB8634A3AA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {0A282BF4-0957-4074-8D5E-C2FB8634A3AA}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0A282BF4-0957-4074-8D5E-C2FB8634A3AA}.Release|x86.ActiveCfg = Release|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Debug|x86.ActiveCfg = Debug|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Release|Any CPU.Build.0 = Release|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB}.Release|x86.ActiveCfg = Release|Any CPU {DD48B2D0-E996-412D-9157-821ED8B17A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD48B2D0-E996-412D-9157-821ED8B17A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD48B2D0-E996-412D-9157-821ED8B17A9D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -103,16 +89,6 @@ Global {6A9E04E7-6229-4A3E-B94A-DA168E962B5A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {6A9E04E7-6229-4A3E-B94A-DA168E962B5A}.Release|Mixed Platforms.Build.0 = Release|Any CPU {6A9E04E7-6229-4A3E-B94A-DA168E962B5A}.Release|x86.ActiveCfg = Release|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Release|Any CPU.Build.0 = Release|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C4693C58-BB66-4630-984E-313E1BCC2474}.Release|x86.ActiveCfg = Release|Any CPU {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -123,39 +99,50 @@ Global {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Release|Mixed Platforms.Build.0 = Release|Any CPU {389628F6-2FB4-4AFF-BACC-16726C44FFA4}.Release|x86.ActiveCfg = Release|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Debug|x86.ActiveCfg = Debug|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Release|Any CPU.Build.0 = Release|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D}.Release|x86.ActiveCfg = Release|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Debug|x86.ActiveCfg = Debug|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Release|Any CPU.Build.0 = Release|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {5E51EFBF-329F-4D3A-B86E-CC111697746F}.Release|x86.ActiveCfg = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|x86.ActiveCfg = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Debug|x86.Build.0 = Debug|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|Any CPU.Build.0 = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|x86.ActiveCfg = Release|Any CPU + {B83EB057-56C7-4296-B0C7-E41F4F70CB02}.Release|x86.Build.0 = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Debug|x86.Build.0 = Debug|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|Any CPU.Build.0 = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|x86.ActiveCfg = Release|Any CPU + {80105366-8EF9-494D-A296-E100E82224CF}.Release|x86.Build.0 = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|x86.ActiveCfg = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Debug|x86.Build.0 = Debug|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|x86.ActiveCfg = Release|Any CPU + {7EF57153-179F-449A-9C81-BD049B1B5613}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {0A282BF4-0957-4074-8D5E-C2FB8634A3AA} = {2904B75F-8F07-4C07-BABC-BC42533B3E75} - {85118B5C-6CAA-44B4-87EA-419DC6F4F2FB} = {EF5E77D7-74B1-422D-B34E-AED35E19249E} {758357F6-CD31-4337-80C4-BA377FC257AF} = {2904B75F-8F07-4C07-BABC-BC42533B3E75} - {C4693C58-BB66-4630-984E-313E1BCC2474} = {EF5E77D7-74B1-422D-B34E-AED35E19249E} - {2F2FBB64-FE67-4204-8E27-E1DB39ED843D} = {2904B75F-8F07-4C07-BABC-BC42533B3E75} - {5E51EFBF-329F-4D3A-B86E-CC111697746F} = {EF5E77D7-74B1-422D-B34E-AED35E19249E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35;packages\Unity.2.1.505.0\lib\NET35 + {80105366-8EF9-494D-A296-E100E82224CF} = {2904B75F-8F07-4C07-BABC-BC42533B3E75} + {7EF57153-179F-449A-9C81-BD049B1B5613} = {2904B75F-8F07-4C07-BABC-BC42533B3E75} EndGlobalSection EndGlobal diff --git a/src/NServiceBus.sln.DotSettings b/src/NServiceBus.sln.DotSettings index ddcbdb9c4e8..bb9e9963748 100644 --- a/src/NServiceBus.sln.DotSettings +++ b/src/NServiceBus.sln.DotSettings @@ -5,7 +5,11 @@ True True False + + True SOLUTION + SUGGESTION + SUGGESTION DO_NOT_SHOW ERROR DO_NOT_SHOW @@ -78,6 +82,7 @@ ERROR ERROR ERROR + ERROR ERROR ERROR ERROR @@ -126,12 +131,15 @@ WARNING ERROR HINT + WARNING ERROR ERROR ERROR <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> Default: Reformat Code Format My Code Using "Particular" conventions + Implicit + Implicit False False ALWAYS_ADD @@ -148,6 +156,8 @@ CHOP_ALWAYS True True + ZeroIndent + ZeroIndent <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> <TypePattern DisplayName="COM interfaces or structs"> @@ -186,7 +196,7 @@ </Entry.Match> </Entry> <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> + <Entry DisplayName="Test Methods" Priority="100"> <Entry.Match> <And> <Kind Is="Method" /> @@ -199,7 +209,7 @@ </Entry> </TypePattern> <TypePattern DisplayName="Default Pattern"> - <Entry Priority="100" DisplayName="Public Delegates"> + <Entry DisplayName="Public Delegates" Priority="100"> <Entry.Match> <And> <Access Is="Public" /> @@ -210,7 +220,7 @@ <Name /> </Entry.SortBy> </Entry> - <Entry Priority="100" DisplayName="Public Enums"> + <Entry DisplayName="Public Enums" Priority="100"> <Entry.Match> <And> <Access Is="Public" /> @@ -221,20 +231,6 @@ <Name /> </Entry.SortBy> </Entry> - <Entry DisplayName="Static Fields and Constants"> - <Entry.Match> - <Or> - <Kind Is="Constant" /> - <And> - <Kind Is="Field" /> - <Static /> - </And> - </Or> - </Entry.Match> - <Entry.SortBy> - <Kind Order="Constant Field" /> - </Entry.SortBy> - </Entry> <Entry DisplayName="Constructors"> <Entry.Match> <Kind Is="Constructor" /> @@ -251,7 +247,7 @@ </Or> </Entry.Match> </Entry> - <Entry Priority="100" DisplayName="Interface Implementations"> + <Entry DisplayName="Interface Implementations" Priority="100"> <Entry.Match> <And> <Kind Is="Member" /> @@ -273,8 +269,22 @@ </And> </Entry.Match> <Entry.SortBy> + <Access /> <Readonly /> - <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> </Entry.SortBy> </Entry> <Entry DisplayName="Nested Types"> @@ -490,7 +500,7 @@ II.2.12 <HandlesEvent /> CustomLayout True - True + False True False True @@ -575,16 +585,19 @@ II.2.12 <HandlesEvent /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True True True True True True + True True - <data /> <data><IncludeFilters /><ExcludeFilters /></data> \ No newline at end of file diff --git a/src/ReturnToSourceQueue/ErrorManager.cs b/src/ReturnToSourceQueue/ErrorManager.cs deleted file mode 100644 index eebd6264691..00000000000 --- a/src/ReturnToSourceQueue/ErrorManager.cs +++ /dev/null @@ -1,155 +0,0 @@ -namespace NServiceBus.Tools.Management.Errors.ReturnToSourceQueue -{ - using System; - using System.Messaging; - using System.Transactions; - using Faults; - - public class ErrorManager - { - public bool ClusteredQueue { get; set; } - - public virtual Address InputQueue - { - set - { - var path = MsmqUtilities.GetFullPath(value); - var q = new MessageQueue(path); - - if ((!ClusteredQueue) && (!q.Transactional)) - { - throw new ArgumentException(string.Format("Queue '{0}' must be transactional.", q.Path)); - } - - queue = q; - - var messageReadPropertyFilter = new MessagePropertyFilter - { - Body = true, - TimeToBeReceived = true, - Recoverable = true, - Id = true, - ResponseQueue = true, - CorrelationId = true, - Extension = true, - AppSpecific = true, - LookupId = true, - }; - - queue.MessageReadPropertyFilter = messageReadPropertyFilter; - } - } - - public void ReturnAll() - { - foreach (var m in queue.GetAllMessages()) - { - ReturnMessageToSourceQueue(m.Id); - } - } - - /// - /// May throw a timeout exception if a message with the given id cannot be found. - /// - public void ReturnMessageToSourceQueue(string messageId) - { - using (var scope = new TransactionScope()) - { - try - { - var message = queue.ReceiveById(messageId, TimeoutDuration, MessageQueueTransactionType.Automatic); - - var tm = MsmqUtilities.Convert(message); - string failedQ; - if (!tm.Headers.TryGetValue(FaultsHeaderKeys.FailedQ, out failedQ)) - { - Console.WriteLine("ERROR: Message does not have a header indicating from which queue it came. Cannot be automatically returned to queue."); - return; - } - - using (var q = new MessageQueue(MsmqUtilities.GetFullPath(Address.Parse(failedQ)))) - { - q.Send(message, MessageQueueTransactionType.Automatic); - } - - Console.WriteLine("Success."); - scope.Complete(); - } - catch (MessageQueueException ex) - { - if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) - { - Console.WriteLine(NoMessageFoundErrorFormat, messageId); - - uint messageCount = 0; - foreach (var m in queue.GetAllMessages()) - { - messageCount++; - var tm = MsmqUtilities.Convert(m); - - var originalId = GetOriginalId(tm); - - if (string.IsNullOrEmpty(originalId) || messageId != originalId) - { - if (messageCount % ProgressInterval == 0) - { - Console.Write("."); - } - continue; - } - - Console.WriteLine(); - Console.WriteLine("Found message - going to return to queue."); - - using (var tx = new TransactionScope()) - { - var failedQueue = tm.Headers[FaultsHeaderKeys.FailedQ]; - using (var q = new MessageQueue(MsmqUtilities.GetFullPath(Address.Parse(failedQueue)))) - { - q.Send(m, MessageQueueTransactionType.Automatic); - } - - queue.ReceiveByLookupId(MessageLookupAction.Current, m.LookupId, MessageQueueTransactionType.Automatic); - - tx.Complete(); - } - - Console.WriteLine("Success."); - scope.Complete(); - - return; - } - - Console.WriteLine(); - Console.WriteLine(NoMessageFoundInHeadersErrorFormat, messageId); - } - } - } - } - - string GetOriginalId(TransportMessage tm) - { - string originalId; - - if (tm.Headers.TryGetValue("NServiceBus.OriginalId", out originalId)) - { - return originalId; - } - if (tm.Headers.TryGetValue(Headers.MessageId, out originalId)) - { - return originalId; - } - - return null; - } - - - const string NoMessageFoundErrorFormat = "INFO: No message found with ID '{0}'. Checking headers of all messages."; - const string NoMessageFoundInHeadersErrorFormat = "INFO: No message found with ID '{0}' in any headers."; - const uint ProgressInterval = 100; - - TimeSpan TimeoutDuration = TimeSpan.FromSeconds(5); - MessageQueue queue; - - } -} \ No newline at end of file diff --git a/src/ReturnToSourceQueue/Program.cs b/src/ReturnToSourceQueue/Program.cs deleted file mode 100644 index c7fee8c1da4..00000000000 --- a/src/ReturnToSourceQueue/Program.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace ReturnToSourceQueue -{ - using System; - using System.Linq; - using System.Net; - using NServiceBus; - using NServiceBus.Tools.Management.Errors.ReturnToSourceQueue; - - class Program - { - static void Main(string[] args) - { - var errorManager = new ErrorManager(); - - string inputQueue = null; - string messageId = null; - - if (args != null && args.Length > 0) - inputQueue = args[0]; - - if (args != null && args.Length > 1) - messageId = args[1]; - - var script = true; - - if (inputQueue == null) - { - Console.WriteLine("NServiceBus ReturnToSource for MSMQ"); - Console.WriteLine("by Particular Software Ltd. \n"); - - Console.WriteLine("Please enter the error queue you would like to use:"); - inputQueue = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(inputQueue)) - { - Console.WriteLine("No error queue specified"); - Console.WriteLine("\nPress 'Enter' to exit."); - Console.ReadLine(); - return; - } - script = false; - } - - var errorQueueAddress = Address.Parse(inputQueue); - - if(!IsLocalIpAddress(errorQueueAddress.Machine)) - { - Console.WriteLine("Input queue [{0}] resides on a remote machine: [{1}].", errorQueueAddress.Queue, errorQueueAddress.Machine); - Console.WriteLine("Due to networking load, it is advised to refrain from using ReturnToSourceQueue on a remote error queue, unless the error queue resides on a clustered machine."); - if (!script) - { - Console.WriteLine( - "Press 'y' if the error queue resides on a Clustered Machine, otherwise press any key to exit."); - if (Console.ReadKey().Key.ToString().ToLower() != "y") - return; - } - Console.WriteLine(string.Empty); - errorManager.ClusteredQueue = true; - } - - if (messageId == null) - { - Console.WriteLine("Please enter the id of the message you'd like to return to its source queue, or 'all' to do so for all messages in the queue."); - messageId = Console.ReadLine(); - } - - errorManager.InputQueue = errorQueueAddress; - Console.WriteLine("Attempting to return message to source queue. Queue: [{0}], message id: [{1}]. Please stand by.", - errorQueueAddress, messageId); - - try - { - if (messageId == "all") - errorManager.ReturnAll(); - else - errorManager.ReturnMessageToSourceQueue(messageId); - - if (args == null || args.Length == 0) - { - Console.WriteLine("Press 'Enter' to exit."); - Console.ReadLine(); - } - } - catch(Exception e) - { - Console.WriteLine("Could not return message to source queue. Reason: " + e.Message); - Console.WriteLine(e.StackTrace); - - Console.WriteLine("\nPress 'Enter' to exit."); - Console.ReadLine(); - } - } - public static bool IsLocalIpAddress(string host) - { - // get host IP addresses - var hostIPs = Dns.GetHostAddresses(host); - // get local IP addresses - var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); - - // test if any host IP equals to any local IP or to localhost - foreach (var hostIP in hostIPs) - { - // is localhost - if (IPAddress.IsLoopback(hostIP)) return true; - // is local address - if (localIPs.Contains(hostIP)) return true; - } - return false; - } - } -} diff --git a/src/ReturnToSourceQueue/Properties/AssemblyInfo.cs b/src/ReturnToSourceQueue/Properties/AssemblyInfo.cs deleted file mode 100644 index 6f7662452de..00000000000 --- a/src/ReturnToSourceQueue/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("ReturnToSourceQueue")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCopyright("Copyright 2010-2014 NServiceBus. All rights reserved")] -[assembly: AssemblyProduct("NServiceBus")] -[assembly: AssemblyCompany("NServiceBus Ltd.")] -[assembly: ComVisible(false)] diff --git a/src/ReturnToSourceQueue/ReturnToSourceQueue.csproj b/src/ReturnToSourceQueue/ReturnToSourceQueue.csproj deleted file mode 100644 index daedc1b5a0d..00000000000 --- a/src/ReturnToSourceQueue/ReturnToSourceQueue.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {C4693C58-BB66-4630-984E-313E1BCC2474} - Exe - Properties - ReturnToSourceQueue - ReturnToSourceQueue - true - ..\NServiceBus.snk - ..\ - v4.5 - - False - - - true - full - false - ..\..\binaries\Tools\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - 618 - true - false - - - pdbonly - true - ..\..\binaries\Tools\ - TRACE - prompt - 4 - AllRules.ruleset - 618 - true - false - - - true - - - - ..\packages\GitVersion.1.2.0\Lib\Net45\GitVersionCore.dll - - - - - - - - - - - - - - - - {dd48b2d0-e996-412d-9157-821ed8b17a9d} - NServiceBus.Core - - - - - - - \ No newline at end of file diff --git a/src/ReturnToSourceQueue/packages.config b/src/ReturnToSourceQueue/packages.config deleted file mode 100644 index e48bcbcf73f..00000000000 --- a/src/ReturnToSourceQueue/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tools/IlMerge/ilmerge.exclude b/tools/IlMerge/ilmerge.exclude index 4b11327b258..77165409503 100644 --- a/tools/IlMerge/ilmerge.exclude +++ b/tools/IlMerge/ilmerge.exclude @@ -1,2 +1 @@ NServiceBus* -Rhino.Mocks.* \ No newline at end of file