Skip to content
This repository was archived by the owner on Apr 29, 2022. It is now read-only.

Commit 694e4e8

Browse files
Merge pull request #18 from AntonyVorontsov/master
Backwards compatibility for pattern matching (wildcards).
2 parents e6134e6 + 053947c commit 694e4e8

23 files changed

+1481
-928
lines changed

LICENSE.txt

Lines changed: 21 additions & 674 deletions
Large diffs are not rendered by default.

RabbitMQ.Client.Core.DependencyInjection.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ ProjectSection(SolutionItems) = preProject
3838
docs\images\delayed-message-model.png = docs\images\delayed-message-model.png
3939
EndProjectSection
4040
EndProject
41+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D0289B2-C566-46CC-A53A-471BCBA0F277}"
42+
EndProject
43+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RabbitMQ.Client.Core.DependencyInjection.Tests", "tests\RabbitMQ.Client.Core.DependencyInjection.Tests\RabbitMQ.Client.Core.DependencyInjection.Tests.csproj", "{85907F19-00B0-4BA6-9B7C-0452A174903D}"
44+
EndProject
4145
Global
4246
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4347
Debug|Any CPU = Debug|Any CPU
@@ -60,6 +64,10 @@ Global
6064
{1F81E848-7781-448F-BBBB-6A1E509B76CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
6165
{1F81E848-7781-448F-BBBB-6A1E509B76CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
6266
{1F81E848-7781-448F-BBBB-6A1E509B76CC}.Release|Any CPU.Build.0 = Release|Any CPU
67+
{85907F19-00B0-4BA6-9B7C-0452A174903D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68+
{85907F19-00B0-4BA6-9B7C-0452A174903D}.Debug|Any CPU.Build.0 = Debug|Any CPU
69+
{85907F19-00B0-4BA6-9B7C-0452A174903D}.Release|Any CPU.ActiveCfg = Release|Any CPU
70+
{85907F19-00B0-4BA6-9B7C-0452A174903D}.Release|Any CPU.Build.0 = Release|Any CPU
6371
EndGlobalSection
6472
GlobalSection(SolutionProperties) = preSolution
6573
HideSolutionNode = FALSE
@@ -70,6 +78,7 @@ Global
7078
{6E75E157-373A-4056-898B-3713CDD5C06C} = {04EFD8F7-5120-4072-9728-64203F6F4873}
7179
{1F81E848-7781-448F-BBBB-6A1E509B76CC} = {04EFD8F7-5120-4072-9728-64203F6F4873}
7280
{93D59B0E-856C-4260-B50E-57FE5C0F5073} = {BC4CDBDE-4AEE-44B3-B00B-380EB1AC5E62}
81+
{85907F19-00B0-4BA6-9B7C-0452A174903D} = {9D0289B2-C566-46CC-A53A-471BCBA0F277}
7382
EndGlobalSection
7483
GlobalSection(ExtensibilityGlobals) = postSolution
7584
SolutionGuid = {0EBBD182-65B2-47F9-ABBE-64B5B8C9652F}

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,6 @@ All notable changes being tracked in the [changelog](./docs/changelog.md) file.
175175

176176
## License
177177

178-
This library licenced under GNU General Public License v3. That means you are free to use it anywhere you want, but if you modify library by yourself you have to provide all notable changes to the community.
178+
This library licenced under MIT license.
179179

180180
Feel free to contribute!
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using RabbitMQ.Client.Core.DependencyInjection.Models;
4+
5+
namespace RabbitMQ.Client.Core.DependencyInjection.Extensions
6+
{
7+
/// <summary>
8+
/// An extension class that contains functionality of pattern (wildcard) matching.
9+
/// </summary>
10+
/// <remarks>
11+
/// Methods of that class allows finding route patterns by which message handlers are "listening" for messages.
12+
/// </remarks>
13+
public static class WildcardExtensions
14+
{
15+
const string Separator = ".";
16+
const string SingleWordPattern = "*";
17+
const string MultipleWordsPattern = "#";
18+
19+
/// <summary>
20+
/// Construct tree (trie) structure of message handler route patterns.
21+
/// </summary>
22+
/// <param name="routePatterns">
23+
/// Collection of message handler route patterns, which are used by them for a message "listening".
24+
/// </param>
25+
/// <returns>
26+
/// Collection of tree nodes <see cref="TreeNode"/>.
27+
/// Depending on routing key bindings that collection can be flat or treelike.
28+
/// </returns>
29+
public static IEnumerable<TreeNode> ConstructRoutesTree(IEnumerable<string> routePatterns)
30+
{
31+
var tree = new List<TreeNode>();
32+
33+
foreach (var binding in routePatterns)
34+
{
35+
var keyParts = binding.Split(Separator);
36+
TreeNode parentTreeNode = null;
37+
var currentTreeNode = tree;
38+
for (var index = 0; index < keyParts.Length; index++)
39+
{
40+
var part = keyParts[index];
41+
42+
var existingNode = index == keyParts.Length - 1
43+
? currentTreeNode.FirstOrDefault(x => x.KeyPartition == part && x.IsLastNode)
44+
: currentTreeNode.FirstOrDefault(x => x.KeyPartition == part && !x.IsLastNode);
45+
46+
if (existingNode is null)
47+
{
48+
var node = new TreeNode
49+
{
50+
Parent = parentTreeNode,
51+
KeyPartition = part,
52+
IsLastNode = index == keyParts.Length - 1
53+
};
54+
currentTreeNode.Add(node);
55+
currentTreeNode = node.Nodes;
56+
parentTreeNode = node;
57+
}
58+
else
59+
{
60+
currentTreeNode = existingNode.Nodes;
61+
parentTreeNode = existingNode;
62+
}
63+
}
64+
}
65+
66+
return tree;
67+
}
68+
69+
/// <summary>
70+
/// Get route patterns that match the given routing key.
71+
/// </summary>
72+
/// <param name="tree">Collection (tree, trie) of nodes.</param>
73+
/// <param name="routingKeyParts">Array of routing key parts split by dots.</param>
74+
/// <returns>Collection of route patterns that correspond to the given routing key.</returns>
75+
public static IEnumerable<string> GetMatchingRoutePatterns(IEnumerable<TreeNode> tree, string[] routingKeyParts)
76+
{
77+
return GetMatchingRoutePatterns(tree, routingKeyParts, depth: 0);
78+
}
79+
80+
static IEnumerable<string> GetMatchingRoutePatterns(IEnumerable<TreeNode> tree, string[] routingKeyParts, int depth)
81+
{
82+
foreach (var node in tree)
83+
{
84+
var matchingPart = routingKeyParts[depth];
85+
if (node.KeyPartition == MultipleWordsPattern)
86+
{
87+
if (!node.Nodes.Any())
88+
{
89+
yield return CollectRoutingKeyInReverseOrder(node);
90+
}
91+
else
92+
{
93+
var tails = CollectRoutingKeyTails(routingKeyParts, depth);
94+
foreach (var tail in tails)
95+
{
96+
var routes = GetMatchingRoutePatterns(node.Nodes, tail, depth: 0);
97+
foreach (var route in routes)
98+
{
99+
yield return route;
100+
}
101+
}
102+
}
103+
}
104+
else if(node.KeyPartition == SingleWordPattern || node.KeyPartition == matchingPart)
105+
{
106+
if (routingKeyParts.Length == depth + 1 && !node.Nodes.Any())
107+
{
108+
yield return CollectRoutingKeyInReverseOrder(node);
109+
}
110+
else if (routingKeyParts.Length != depth + 1 && node.Nodes.Any())
111+
{
112+
var routes = GetMatchingRoutePatterns(node.Nodes, routingKeyParts, depth + 1).ToList();
113+
foreach (var route in routes)
114+
{
115+
yield return route;
116+
}
117+
}
118+
}
119+
}
120+
}
121+
122+
static IEnumerable<string[]> CollectRoutingKeyTails(IReadOnlyCollection<string> routingKeyParts, int depthStart)
123+
{
124+
for (var index = depthStart; index < routingKeyParts.Count; index++)
125+
{
126+
yield return routingKeyParts.Skip(index).ToArray();
127+
}
128+
}
129+
130+
static string CollectRoutingKeyInReverseOrder(TreeNode node, string routingKey = "")
131+
{
132+
routingKey = string.IsNullOrEmpty(routingKey) ? node.KeyPartition : $"{node.KeyPartition}.{routingKey}";
133+
return node.Parent != null ? CollectRoutingKeyInReverseOrder(node.Parent, routingKey) : routingKey;
134+
}
135+
}
136+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Collections.Generic;
2+
using RabbitMQ.Client.Core.DependencyInjection.Models;
3+
4+
namespace RabbitMQ.Client.Core.DependencyInjection
5+
{
6+
/// <summary>
7+
/// Interface of the service that build message handler containers collection.
8+
/// </summary>
9+
public interface IMessageHandlerContainerBuilder
10+
{
11+
/// <summary>
12+
/// Build message handler containers collection.
13+
/// </summary>
14+
/// <returns>Collection of message handler containers <see cref="MessageHandlerContainer"/>.</returns>
15+
IEnumerable<MessageHandlerContainer> BuildCollection();
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using RabbitMQ.Client.Events;
2+
3+
namespace RabbitMQ.Client.Core.DependencyInjection
4+
{
5+
/// <summary>
6+
/// An interface of the service that contains logic of handling message receiving (consumption) events and passing those messages to the message handlers.
7+
/// </summary>
8+
public interface IMessageHandlingService
9+
{
10+
/// <summary>
11+
/// Handle message receiving event.
12+
/// </summary>
13+
/// <param name="eventArgs">Arguments of message receiving event.</param>
14+
/// <param name="queueService">An instance of the queue service <see cref="IQueueService"/>.</param>
15+
void HandleMessageReceivingEvent(BasicDeliverEventArgs eventArgs, IQueueService queueService);
16+
}
17+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using RabbitMQ.Client.Core.DependencyInjection.Extensions;
5+
using RabbitMQ.Client.Core.DependencyInjection.Models;
6+
7+
namespace RabbitMQ.Client.Core.DependencyInjection
8+
{
9+
/// <summary>
10+
/// Implementation of the service that build message handler containers collection.
11+
/// Those containers contain information about message handlers (all types) bound to the exchange.
12+
/// Container could be "general" if message handler has not been bound to the exchange (so it will "listen" regardless of the exchange).
13+
/// </summary>
14+
public class MessageHandlerContainerBuilder : IMessageHandlerContainerBuilder
15+
{
16+
readonly IEnumerable<MessageHandlerRouter> _routers;
17+
readonly IEnumerable<IMessageHandler> _messageHandlers;
18+
readonly IEnumerable<IAsyncMessageHandler> _asyncMessageHandlers;
19+
readonly IEnumerable<INonCyclicMessageHandler> _nonCyclicHandlers;
20+
readonly IEnumerable<IAsyncNonCyclicMessageHandler> _asyncNonCyclicHandlers;
21+
22+
public MessageHandlerContainerBuilder(
23+
IEnumerable<MessageHandlerRouter> routers,
24+
IEnumerable<IMessageHandler> messageHandlers,
25+
IEnumerable<IAsyncMessageHandler> asyncMessageHandlers,
26+
IEnumerable<INonCyclicMessageHandler> nonCyclicHandlers,
27+
IEnumerable<IAsyncNonCyclicMessageHandler> asyncNonCyclicHandlers)
28+
{
29+
_routers = routers;
30+
_messageHandlers = messageHandlers;
31+
_asyncMessageHandlers = asyncMessageHandlers;
32+
_nonCyclicHandlers = nonCyclicHandlers;
33+
_asyncNonCyclicHandlers = asyncNonCyclicHandlers;
34+
}
35+
36+
/// <summary>
37+
/// Build message handler containers collection.
38+
/// </summary>
39+
/// <returns>Collection of message handler containers <see cref="MessageHandlerContainer"/>.</returns>
40+
public IEnumerable<MessageHandlerContainer> BuildCollection()
41+
{
42+
var containers = new List<MessageHandlerContainer>();
43+
var generalRouters = _routers.Where(x => x.IsGeneral).ToList();
44+
if (generalRouters.Any())
45+
{
46+
var container = CreateContainer(null, generalRouters);
47+
containers.Add(container);
48+
}
49+
50+
var exchanges = _routers.Where(x => !x.IsGeneral).Select(x => x.Exchange).Distinct().ToList();
51+
foreach (var exchange in exchanges)
52+
{
53+
var exchangeRouters = _routers.Where(x => x.Exchange == exchange).ToList();
54+
var container = CreateContainer(exchange, exchangeRouters);
55+
containers.Add(container);
56+
}
57+
return containers;
58+
}
59+
60+
MessageHandlerContainer CreateContainer(string exchange, IEnumerable<MessageHandlerRouter> selectedRouters)
61+
{
62+
var routersDictionary = TransformMessageHandlerRoutersToDictionary(selectedRouters);
63+
var boundMessageHandlers = _messageHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType()));
64+
var boundAsyncMessageHandlers = _asyncMessageHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType()));
65+
var boundNonCyclicMessageHandlers = _nonCyclicHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType()));
66+
var boundAsyncNonCyclicMessageHandlers = _asyncNonCyclicHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType()));
67+
var routePatterns = selectedRouters.SelectMany(x => x.RoutePatterns).Distinct().ToList();
68+
return new MessageHandlerContainer
69+
{
70+
Exchange = exchange,
71+
Tree = WildcardExtensions.ConstructRoutesTree(routePatterns),
72+
MessageHandlers = TransformMessageHandlersCollectionToDictionary(boundMessageHandlers, routersDictionary),
73+
AsyncMessageHandlers = TransformMessageHandlersCollectionToDictionary(boundAsyncMessageHandlers, routersDictionary),
74+
NonCyclicHandlers = TransformMessageHandlersCollectionToDictionary(boundNonCyclicMessageHandlers, routersDictionary),
75+
AsyncNonCyclicHandlers = TransformMessageHandlersCollectionToDictionary(boundAsyncNonCyclicMessageHandlers, routersDictionary)
76+
};
77+
}
78+
79+
static IDictionary<Type, List<string>> TransformMessageHandlerRoutersToDictionary(IEnumerable<MessageHandlerRouter> routers)
80+
{
81+
var dictionary = new Dictionary<Type, List<string>>();
82+
foreach (var router in routers)
83+
{
84+
if (dictionary.ContainsKey(router.Type))
85+
{
86+
dictionary[router.Type] = dictionary[router.Type].Union(router.RoutePatterns).ToList();
87+
}
88+
else
89+
{
90+
dictionary.Add(router.Type, router.RoutePatterns);
91+
}
92+
}
93+
return dictionary;
94+
}
95+
96+
static IDictionary<string, IList<T>> TransformMessageHandlersCollectionToDictionary<T>(
97+
IEnumerable<T> messageHandlers,
98+
IDictionary<Type, List<string>> routersDictionary)
99+
{
100+
var dictionary = new Dictionary<string, IList<T>>();
101+
foreach (var handler in messageHandlers)
102+
{
103+
var type = handler.GetType();
104+
foreach (var routingKey in routersDictionary[type])
105+
{
106+
if (dictionary.ContainsKey(routingKey))
107+
{
108+
if (!dictionary[routingKey].Any(x => x.GetType() == handler.GetType()))
109+
{
110+
dictionary[routingKey].Add(handler);
111+
}
112+
}
113+
else
114+
{
115+
dictionary.Add(routingKey, new List<T> { handler });
116+
}
117+
}
118+
}
119+
return dictionary;
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)