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 { /// /// /// Default implementation of the Tapeti config builder. /// Automatically registers the default middleware for injecting the message parameter and handling the return value. /// public class TapetiConfig : ITapetiConfigBuilderAccess { private Config? config; private readonly List bindingMiddleware = new(); /// public IDependencyResolver DependencyResolver => GetConfig().DependencyResolver; /// /// Instantiates a new Tapeti config builder. /// /// A wrapper implementation for an IoC container to allow dependency injection 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()); } /// public ITapetiConfig Build() { if (config == null) throw new InvalidOperationException("TapetiConfig.Build must only be called once"); RegisterDefaults(); (config.DependencyResolver as IDependencyContainer)?.RegisterDefaultSingleton(config); var outputConfig = config; config = null; outputConfig.Lock(); return outputConfig; } /// public ITapetiConfigBuilder Use(IControllerBindingMiddleware handler) { bindingMiddleware.Add(handler); return this; } /// public ITapetiConfigBuilder Use(IMessageMiddleware handler) { GetConfig().Use(handler); return this; } /// public ITapetiConfigBuilder Use(IPublishMiddleware handler) { GetConfig().Use(handler); return this; } /// 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; } /// public void RegisterBinding(IBinding binding) { GetConfig().RegisterBinding(binding); } /// public ITapetiConfigBuilder DisablePublisherConfirms() { GetConfig().SetPublisherConfirms(false); return this; } /// public ITapetiConfigBuilder SetPublisherConfirms(bool enabled) { GetConfig().SetPublisherConfirms(enabled); return this; } /// public ITapetiConfigBuilder EnableDeclareDurableQueues() { GetConfig().SetDeclareDurableQueues(true); return this; } /// public ITapetiConfigBuilder SetDeclareDurableQueues(bool enabled) { GetConfig().SetDeclareDurableQueues(enabled); return this; } /// public ITapetiConfigBuilder DisableVerifyDurableQueues() { GetConfig().SetVerifyDurableQueues(false); return this; } /// public ITapetiConfigBuilder SetVerifyDurableQueues(bool enabled) { GetConfig().SetVerifyDurableQueues(enabled); return this; } /// /// Registers the default implementation of various Tapeti interfaces into the IoC container. /// protected void RegisterDefaults() { if (DependencyResolver is not IDependencyContainer container) return; if (ConsoleHelper.IsAvailable()) container.RegisterDefault(); else container.RegisterDefault(); container.RegisterDefault(); container.RegisterDefault(); container.RegisterDefault(); container.RegisterDefault(); } /// 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; } /// 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 messageMiddleware = new(); private readonly List publishMiddleware = new(); public IReadOnlyList Message => messageMiddleware; public IReadOnlyList Publish => publishMiddleware; public void Use(IMessageMiddleware handler) { messageMiddleware.Add(handler); } public void Use(IPublishMiddleware handler) { publishMiddleware.Add(handler); } } internal class ConfigBindings : List, ITapetiConfigBindings { private Dictionary? 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() .ToDictionary(binding => binding.Method, binding => binding); } } } }