Tapeti/Tapeti/TapetiConfig.cs

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);
}
}
}
}