2022-11-17 15:47:07 +00:00
using System ;
2020-02-12 10:34:51 +00:00
using System.Linq ;
using System.Reflection ;
2022-11-23 08:13:38 +00:00
using System.Text ;
2023-04-13 06:39:43 +00:00
using Tapeti.Config.Annotations ;
2023-04-14 13:47:27 +00:00
using Tapeti.Config ;
2022-11-22 12:20:47 +00:00
using Tapeti.Connection ;
2020-02-12 10:34:51 +00:00
using Tapeti.Default ;
// ReSharper disable UnusedMember.Global
namespace Tapeti
{
/// <summary>
/// Thrown when an issue is detected in a controller configuration.
/// </summary>
public class TopologyConfigurationException : Exception
{
/// <inheritdoc />
public TopologyConfigurationException ( string message ) : base ( message ) { }
}
/// <summary>
/// Extension methods for registering message controllers.
/// </summary>
public static class TapetiConfigControllers
{
/// <summary>
/// Registers all public methods in the specified controller class as message handlers.
/// </summary>
/// <param name="builder"></param>
/// <param name="controller">The controller class to register. The class and/or methods must be annotated with either the DurableQueue or DynamicQueue attribute.</param>
public static ITapetiConfigBuilder RegisterController ( this ITapetiConfigBuilder builder , Type controller )
{
var builderAccess = ( ITapetiConfigBuilderAccess ) builder ;
if ( ! controller . IsClass )
throw new ArgumentException ( $"Controller {controller.Name} must be a class" ) ;
2022-11-21 15:59:09 +00:00
var controllerQueueInfo = GetQueueInfo ( controller , null ) ;
2020-02-12 10:34:51 +00:00
( builderAccess . DependencyResolver as IDependencyContainer ) ? . RegisterController ( controller ) ;
var controllerIsObsolete = controller . GetCustomAttribute < ObsoleteAttribute > ( ) ! = null ;
2022-02-09 10:26:56 +00:00
foreach ( var method in controller . GetMembers ( BindingFlags . Public | BindingFlags . Instance | BindingFlags . Static )
2020-02-12 10:34:51 +00:00
. Where ( m = > m . MemberType = = MemberTypes . Method & & m . DeclaringType ! = typeof ( object ) & & ( m as MethodInfo ) ? . IsSpecialName = = false )
. Select ( m = > ( MethodInfo ) m ) )
{
2023-04-13 06:39:43 +00:00
if ( method . GetCustomAttributes < NoBindingAttribute > ( ) . Any ( ) )
continue ;
2020-02-12 10:34:51 +00:00
var methodIsObsolete = controllerIsObsolete | | method . GetCustomAttribute < ObsoleteAttribute > ( ) ! = null ;
2022-11-23 08:13:38 +00:00
var context = new ControllerBindingContext ( controller , method , method . GetParameters ( ) , method . ReturnParameter ) ;
2020-02-12 10:34:51 +00:00
2023-04-14 13:47:27 +00:00
if ( method . GetResponseHandlerAttribute ( ) ! = null )
2023-04-13 06:39:43 +00:00
{
2020-02-12 10:34:51 +00:00
context . SetBindingTargetMode ( BindingTargetMode . Direct ) ;
2023-04-13 06:39:43 +00:00
context . Use ( new ResponseFilterMiddleware ( ) ) ;
}
2020-02-12 10:34:51 +00:00
var allowBinding = false ;
builderAccess . ApplyBindingMiddleware ( context , ( ) = > { allowBinding = true ; } ) ;
if ( ! allowBinding )
continue ;
if ( context . MessageClass = = null )
throw new TopologyConfigurationException ( $"Method {method.Name} in controller {controller.Name} does not resolve to a message class" ) ;
var invalidBindings = context . Parameters . Where ( p = > ! p . HasBinding ) . ToList ( ) ;
if ( invalidBindings . Count > 0 )
{
var parameterNames = string . Join ( ", " , invalidBindings . Select ( p = > p . Info . Name ) ) ;
throw new TopologyConfigurationException ( $"Method {method.Name} in controller {method.DeclaringType?.Name} has unknown parameters: {parameterNames}" ) ;
}
2022-11-21 15:59:09 +00:00
var methodQueueInfo = GetQueueInfo ( method , controllerQueueInfo ) ;
2022-11-17 15:47:07 +00:00
if ( methodQueueInfo is not { IsValid : true } )
2020-06-11 14:38:32 +00:00
throw new TopologyConfigurationException (
$"Method {method.Name} or controller {controller.Name} requires a queue attribute" ) ;
2020-02-12 10:34:51 +00:00
builder . RegisterBinding ( new ControllerMethodBinding ( builderAccess . DependencyResolver , new ControllerMethodBinding . BindingInfo
{
ControllerType = controller ,
Method = method ,
QueueInfo = methodQueueInfo ,
MessageClass = context . MessageClass ,
BindingTargetMode = context . BindingTargetMode ,
IsObsolete = methodIsObsolete ,
ParameterFactories = context . GetParameterHandlers ( ) ,
ResultHandler = context . GetResultHandler ( ) ,
FilterMiddleware = context . Middleware . Where ( m = > m is IControllerFilterMiddleware ) . Cast < IControllerFilterMiddleware > ( ) . ToList ( ) ,
MessageMiddleware = context . Middleware . Where ( m = > m is IControllerMessageMiddleware ) . Cast < IControllerMessageMiddleware > ( ) . ToList ( ) ,
CleanupMiddleware = context . Middleware . Where ( m = > m is IControllerCleanupMiddleware ) . Cast < IControllerCleanupMiddleware > ( ) . ToList ( )
} ) ) ;
}
return builder ;
}
2023-04-13 06:39:43 +00:00
/// <inheritdoc cref="RegisterController"/>
public static ITapetiConfigBuilder RegisterController < TController > ( this ITapetiConfigBuilder builder ) where TController : class
{
return RegisterController ( builder , typeof ( TController ) ) ;
}
2020-02-12 10:34:51 +00:00
/// <summary>
/// Registers all controllers in the specified assembly which are marked with the MessageController attribute.
/// </summary>
/// <param name="builder"></param>
/// <param name="assembly">The assembly to scan for controllers.</param>
public static ITapetiConfigBuilder RegisterAllControllers ( this ITapetiConfigBuilder builder , Assembly assembly )
{
2023-04-14 13:47:27 +00:00
foreach ( var type in assembly . GetTypes ( ) . Where ( t = > t . HasMessageControllerAttribute ( ) ) )
2020-02-12 10:34:51 +00:00
RegisterController ( builder , type ) ;
return builder ;
}
/// <summary>
/// Registers all controllers in the entry assembly which are marked with the MessageController attribute.
/// </summary>
/// <param name="builder"></param>
public static ITapetiConfigBuilder RegisterAllControllers ( this ITapetiConfigBuilder builder )
{
2022-11-23 08:13:38 +00:00
var assembly = Assembly . GetEntryAssembly ( ) ;
if ( assembly = = null )
throw new InvalidOperationException ( "No EntryAssembly" ) ;
return RegisterAllControllers ( builder , assembly ) ;
2020-02-12 10:34:51 +00:00
}
2022-11-23 08:13:38 +00:00
private static ControllerMethodBinding . QueueInfo ? GetQueueInfo ( MemberInfo member , ControllerMethodBinding . QueueInfo ? fallbackQueueInfo )
2020-02-12 10:34:51 +00:00
{
2023-04-14 13:47:27 +00:00
var dynamicQueueAttribute = member . GetDynamicQueueAttribute ( ) ;
var durableQueueAttribute = member . GetDurableQueueAttribute ( ) ;
var queueArgumentsAttribute = member . GetQueueArgumentsAttribute ( ) ;
2020-02-12 10:34:51 +00:00
if ( dynamicQueueAttribute ! = null & & durableQueueAttribute ! = null )
throw new TopologyConfigurationException ( $"Cannot combine static and dynamic queue attributes on controller {member.DeclaringType?.Name} method {member.Name}" ) ;
2022-11-21 15:59:09 +00:00
if ( dynamicQueueAttribute = = null & & durableQueueAttribute = = null & & ( queueArgumentsAttribute = = null | | fallbackQueueInfo = = null ) )
return fallbackQueueInfo ;
2022-11-17 15:47:07 +00:00
2022-11-21 15:59:09 +00:00
QueueType queueType ;
2023-04-14 13:47:27 +00:00
string? name ;
2022-11-21 15:59:09 +00:00
2022-11-17 15:47:07 +00:00
2020-02-12 10:34:51 +00:00
if ( dynamicQueueAttribute ! = null )
2022-11-21 15:59:09 +00:00
{
queueType = QueueType . Dynamic ;
name = dynamicQueueAttribute . Prefix ;
}
else if ( durableQueueAttribute ! = null )
{
queueType = QueueType . Durable ;
name = durableQueueAttribute . Name ;
}
else
{
2022-11-23 08:13:38 +00:00
queueType = fallbackQueueInfo ! . QueueType ;
2022-11-21 15:59:09 +00:00
name = fallbackQueueInfo . Name ;
}
2020-02-12 10:34:51 +00:00
2022-11-23 08:13:38 +00:00
return new ControllerMethodBinding . QueueInfo ( queueType , name )
2022-11-21 15:59:09 +00:00
{
QueueArguments = GetQueueArguments ( queueArgumentsAttribute ) ? ? fallbackQueueInfo ? . QueueArguments
} ;
2020-02-12 10:34:51 +00:00
}
2022-11-17 15:47:07 +00:00
2022-11-23 08:13:38 +00:00
private static IRabbitMQArguments ? GetQueueArguments ( QueueArgumentsAttribute ? queueArgumentsAttribute )
2022-11-17 15:47:07 +00:00
{
if ( queueArgumentsAttribute = = null )
return null ;
2022-11-23 08:13:38 +00:00
var arguments = new RabbitMQArguments ( queueArgumentsAttribute . CustomArguments . ToDictionary (
p = > p . Key ,
p = > p . Value switch
{
string stringValue = > Encoding . UTF8 . GetBytes ( stringValue ) ,
_ = > p . Value
}
2023-04-14 13:47:27 +00:00
) ) ;
2022-11-23 08:13:38 +00:00
2022-11-17 15:47:07 +00:00
if ( queueArgumentsAttribute . MaxLength > 0 )
2022-11-22 12:20:47 +00:00
arguments . Add ( @"x-max-length" , queueArgumentsAttribute . MaxLength ) ;
2022-11-17 15:47:07 +00:00
if ( queueArgumentsAttribute . MaxLengthBytes > 0 )
2022-11-22 12:20:47 +00:00
arguments . Add ( @"x-max-length-bytes" , queueArgumentsAttribute . MaxLengthBytes ) ;
2022-11-17 15:47:07 +00:00
if ( queueArgumentsAttribute . MessageTTL > 0 )
2022-11-22 12:20:47 +00:00
arguments . Add ( @"x-message-ttl" , queueArgumentsAttribute . MessageTTL ) ;
2022-11-17 15:47:07 +00:00
switch ( queueArgumentsAttribute . Overflow )
{
case RabbitMQOverflow . NotSpecified :
break ;
case RabbitMQOverflow . DropHead :
2022-11-22 12:20:47 +00:00
arguments . AddUTF8 ( @"x-overflow" , @"drop-head" ) ;
2022-11-17 15:47:07 +00:00
break ;
case RabbitMQOverflow . RejectPublish :
2022-11-22 12:20:47 +00:00
arguments . AddUTF8 ( @"x-overflow" , @"reject-publish" ) ;
2022-11-17 15:47:07 +00:00
break ;
case RabbitMQOverflow . RejectPublishDeadletter :
2022-11-22 12:20:47 +00:00
arguments . AddUTF8 ( @"x-overflow" , @"reject-publish-dlx" ) ;
2022-11-17 15:47:07 +00:00
break ;
default :
throw new ArgumentOutOfRangeException ( nameof ( queueArgumentsAttribute . Overflow ) , queueArgumentsAttribute . Overflow , "Unsupported Overflow value" ) ;
}
return arguments . Count > 0 ? arguments : null ;
}
2020-02-12 10:34:51 +00:00
}
}