343 lines
10 KiB
C#
343 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Tapeti.Config;
|
|
using Tapeti.Default;
|
|
using Tapeti.Helpers;
|
|
|
|
// ReSharper disable UnusedMember.Global
|
|
|
|
namespace Tapeti
|
|
{
|
|
/// <inheritdoc cref="ITapetiConfigBuilder" />
|
|
/// <summary>
|
|
/// Default implementation of the Tapeti config builder.
|
|
/// Automatically registers the default middleware for injecting the message parameter and handling the return value.
|
|
/// </summary>
|
|
public class TapetiConfig : ITapetiConfigBuilderAccess
|
|
{
|
|
private Config? config;
|
|
private readonly List<IControllerBindingMiddleware> bindingMiddleware = new();
|
|
|
|
|
|
/// <inheritdoc />
|
|
public IDependencyResolver DependencyResolver => GetConfig().DependencyResolver;
|
|
|
|
|
|
/// <summary>
|
|
/// Instantiates a new Tapeti config builder.
|
|
/// </summary>
|
|
/// <param name="dependencyResolver">A wrapper implementation for an IoC container to allow dependency injection</param>
|
|
public TapetiConfig(IDependencyResolver dependencyResolver)
|
|
{
|
|
config = new Config(dependencyResolver);
|
|
|
|
Use(new DependencyResolverBinding());
|
|
Use(new PublishResultBinding());
|
|
Use(new CancellationTokenBinding());
|
|
|
|
// Registered last so it runs first and the MessageClass is known to other middleware
|
|
Use(new MessageBinding());
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfig Build()
|
|
{
|
|
if (config == null)
|
|
throw new InvalidOperationException("TapetiConfig.Build must only be called once");
|
|
|
|
RegisterDefaults();
|
|
(config.DependencyResolver as IDependencyContainer)?.RegisterDefaultSingleton<ITapetiConfig>(config);
|
|
|
|
|
|
var outputConfig = config;
|
|
config = null;
|
|
|
|
outputConfig.Lock();
|
|
return outputConfig;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder Use(IControllerBindingMiddleware handler)
|
|
{
|
|
bindingMiddleware.Add(handler);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder Use(IMessageMiddleware handler)
|
|
{
|
|
GetConfig().Use(handler);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder Use(IPublishMiddleware handler)
|
|
{
|
|
GetConfig().Use(handler);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder Use(ITapetiExtension extension)
|
|
{
|
|
if (DependencyResolver is IDependencyContainer container)
|
|
extension.RegisterDefaults(container);
|
|
|
|
var configInstance = GetConfig();
|
|
|
|
foreach (var middleware in extension.GetMiddleware(DependencyResolver))
|
|
{
|
|
switch (middleware)
|
|
{
|
|
case IControllerBindingMiddleware bindingExtension:
|
|
Use(bindingExtension);
|
|
break;
|
|
|
|
case IMessageMiddleware messageExtension:
|
|
configInstance.Use(messageExtension);
|
|
break;
|
|
|
|
case IPublishMiddleware publishExtension:
|
|
configInstance.Use(publishExtension);
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException(
|
|
$"Unsupported middleware implementation: {middleware.GetType().Name}");
|
|
}
|
|
}
|
|
|
|
var bindingBundle = (extension as ITapetiExtensionBinding)?.GetBindings(DependencyResolver);
|
|
if (bindingBundle == null)
|
|
return this;
|
|
|
|
foreach (var binding in bindingBundle)
|
|
GetConfig().RegisterBinding(binding);
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public void RegisterBinding(IBinding binding)
|
|
{
|
|
GetConfig().RegisterBinding(binding);
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder DisablePublisherConfirms()
|
|
{
|
|
GetConfig().SetPublisherConfirms(false);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder SetPublisherConfirms(bool enabled)
|
|
{
|
|
GetConfig().SetPublisherConfirms(enabled);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder EnableDeclareDurableQueues()
|
|
{
|
|
GetConfig().SetDeclareDurableQueues(true);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder SetDeclareDurableQueues(bool enabled)
|
|
{
|
|
GetConfig().SetDeclareDurableQueues(enabled);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder DisableVerifyDurableQueues()
|
|
{
|
|
GetConfig().SetVerifyDurableQueues(false);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public ITapetiConfigBuilder SetVerifyDurableQueues(bool enabled)
|
|
{
|
|
GetConfig().SetVerifyDurableQueues(enabled);
|
|
return this;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Registers the default implementation of various Tapeti interfaces into the IoC container.
|
|
/// </summary>
|
|
protected void RegisterDefaults()
|
|
{
|
|
if (DependencyResolver is not IDependencyContainer container)
|
|
return;
|
|
|
|
if (ConsoleHelper.IsAvailable())
|
|
container.RegisterDefault<ILogger, ConsoleLogger>();
|
|
else
|
|
container.RegisterDefault<ILogger, DevNullLogger>();
|
|
|
|
container.RegisterDefault<IMessageSerializer, JsonMessageSerializer>();
|
|
container.RegisterDefault<IExchangeStrategy, NamespaceMatchExchangeStrategy>();
|
|
container.RegisterDefault<IRoutingKeyStrategy, TypeNameRoutingKeyStrategy>();
|
|
container.RegisterDefault<IExceptionStrategy, NackExceptionStrategy>();
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public void ApplyBindingMiddleware(IControllerBindingContext context, Action lastHandler)
|
|
{
|
|
MiddlewareHelper.Go(bindingMiddleware,
|
|
(handler, next) => handler.Handle(context, next),
|
|
lastHandler);
|
|
}
|
|
|
|
|
|
private Config GetConfig()
|
|
{
|
|
if (config == null)
|
|
throw new InvalidOperationException("TapetiConfig can not be updated after Build");
|
|
|
|
return config;
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
internal class Config : ITapetiConfig
|
|
{
|
|
private readonly ConfigFeatures features = new();
|
|
private readonly ConfigMiddleware middleware = new();
|
|
private readonly ConfigBindings bindings = new();
|
|
|
|
public IDependencyResolver DependencyResolver { get; }
|
|
public ITapetiConfigFeatues Features => features;
|
|
public ITapetiConfigMiddleware Middleware => middleware;
|
|
public ITapetiConfigBindings Bindings => bindings;
|
|
|
|
|
|
public Config(IDependencyResolver dependencyResolver)
|
|
{
|
|
DependencyResolver = dependencyResolver;
|
|
}
|
|
|
|
|
|
public void Lock()
|
|
{
|
|
bindings.Lock();
|
|
}
|
|
|
|
|
|
public void Use(IMessageMiddleware handler)
|
|
{
|
|
middleware.Use(handler);
|
|
}
|
|
|
|
public void Use(IPublishMiddleware handler)
|
|
{
|
|
middleware.Use(handler);
|
|
}
|
|
|
|
|
|
public void RegisterBinding(IBinding binding)
|
|
{
|
|
bindings.Add(binding);
|
|
}
|
|
|
|
|
|
public void SetPublisherConfirms(bool enabled)
|
|
{
|
|
features.PublisherConfirms = enabled;
|
|
}
|
|
|
|
public void SetDeclareDurableQueues(bool enabled)
|
|
{
|
|
features.DeclareDurableQueues = enabled;
|
|
}
|
|
|
|
public void SetVerifyDurableQueues(bool enabled)
|
|
{
|
|
features.VerifyDurableQueues = enabled;
|
|
}
|
|
}
|
|
|
|
|
|
internal class ConfigFeatures : ITapetiConfigFeatues
|
|
{
|
|
public bool PublisherConfirms { get; internal set; } = true;
|
|
public bool DeclareDurableQueues { get; internal set; }
|
|
public bool VerifyDurableQueues { get; internal set; } = true;
|
|
}
|
|
|
|
|
|
internal class ConfigMiddleware : ITapetiConfigMiddleware
|
|
{
|
|
private readonly List<IMessageMiddleware> messageMiddleware = new();
|
|
private readonly List<IPublishMiddleware> publishMiddleware = new();
|
|
|
|
|
|
public IReadOnlyList<IMessageMiddleware> Message => messageMiddleware;
|
|
public IReadOnlyList<IPublishMiddleware> Publish => publishMiddleware;
|
|
|
|
|
|
public void Use(IMessageMiddleware handler)
|
|
{
|
|
messageMiddleware.Add(handler);
|
|
}
|
|
|
|
public void Use(IPublishMiddleware handler)
|
|
{
|
|
publishMiddleware.Add(handler);
|
|
}
|
|
}
|
|
|
|
|
|
internal class ConfigBindings : List<IBinding>, ITapetiConfigBindings
|
|
{
|
|
private Dictionary<MethodInfo, IControllerMethodBinding>? methodLookup;
|
|
|
|
|
|
public IControllerMethodBinding? ForMethod(Delegate method)
|
|
{
|
|
if (methodLookup == null)
|
|
throw new InvalidOperationException("Lock must be called first");
|
|
|
|
return methodLookup.TryGetValue(method.Method, out var binding) ? binding : null;
|
|
}
|
|
|
|
|
|
public IControllerMethodBinding? ForMethod(MethodInfo method)
|
|
{
|
|
if (methodLookup == null)
|
|
throw new InvalidOperationException("Lock must be called first");
|
|
|
|
return methodLookup.TryGetValue(method, out var binding) ? binding : null;
|
|
}
|
|
|
|
|
|
public void Lock()
|
|
{
|
|
methodLookup = this
|
|
.Where(binding => binding is IControllerMethodBinding)
|
|
.Cast<IControllerMethodBinding>()
|
|
.ToDictionary(binding => binding.Method, binding => binding);
|
|
}
|
|
}
|
|
}
|
|
}
|