Line data Source code
1 : #region Copyright
2 : // // -----------------------------------------------------------------------
3 : // // <copyright company="Chinchilla Software Limited">
4 : // // Copyright Chinchilla Software Limited. All rights reserved.
5 : // // </copyright>
6 : // // -----------------------------------------------------------------------
7 : #endregion
8 :
9 : using System;
10 : using System.Collections.Generic;
11 : using System.Linq;
12 : using Cqrs.Commands;
13 : using Cqrs.Events;
14 : using Cqrs.Exceptions;
15 : using Cqrs.Infrastructure;
16 : using Cqrs.Messages;
17 :
18 : namespace Cqrs.Bus
19 : {
20 : /// <summary>
21 : /// Manages <see cref="Route">routes</see>.
22 : /// </summary>
23 : public class RouteManager
24 : : IHandlerRegistrar
25 1 : {
26 : /// <summary>
27 : /// The <see cref="Route"/> to execute per <see cref="Type"/>
28 : /// </summary>
29 : protected IDictionary<Type, Route> Routes { get; private set; }
30 :
31 : /// <summary>
32 : /// A <see cref="Route"/> to execute for all <see cref="IEvent{TAuthenticationToken}"/>
33 : /// </summary>
34 : public Route GlobalEventRoute { get; private set; }
35 :
36 : private static Type CommandType { get; set; }
37 :
38 : private static Type EventType { get; set; }
39 :
40 : /// <summary>
41 : /// Instantiates a new instance of <see cref="RouteManager"/>.
42 : /// </summary>
43 1 : public RouteManager()
44 : {
45 : Routes = new Dictionary<Type, Route>();
46 : GlobalEventRoute = new Route { Handlers = new List<RouteHandlerDelegate>() };
47 : }
48 :
49 : static RouteManager()
50 : {
51 : CommandType = typeof (ICommand<>);
52 : EventType = typeof (IEvent<>);
53 : }
54 :
55 : #region Implementation of IHandlerRegistrar
56 :
57 : /// <summary>
58 : /// Register an event or command handler that will listen and respond to events or commands.
59 : /// </summary>
60 1 : public virtual void RegisterHandler<TMessage>(Action<TMessage> handler, Type targetedType, bool holdMessageLock = true)
61 : where TMessage : IMessage
62 : {
63 : Route route;
64 : if (!Routes.TryGetValue(typeof(TMessage), out route))
65 : {
66 : route = new Route
67 : {
68 : Handlers = new List<RouteHandlerDelegate>()
69 : };
70 : Routes.Add(typeof(TMessage), route);
71 : }
72 : route.Handlers.Add
73 : (
74 : new RouteHandlerDelegate
75 : {
76 : Delegate = DelegateAdjuster.CastArgument<IMessage, TMessage>(x => handler(x)),
77 : TargetedType = targetedType
78 : }
79 : );
80 : }
81 :
82 : /// <summary>
83 : /// Register an event or command handler that will listen and respond to events or commands.
84 : /// </summary>
85 1 : public void RegisterHandler<TMessage>(Action<TMessage> handler, bool holdMessageLock = true)
86 : where TMessage : IMessage
87 : {
88 : RegisterHandler(handler, null, holdMessageLock);
89 : }
90 :
91 : /// <summary>
92 : /// Register an event handler that will listen and respond to all events.
93 : /// </summary>
94 1 : public void RegisterGlobalEventHandler<TMessage>(Action<TMessage> handler, bool holdMessageLock = true) where TMessage : IMessage
95 : {
96 : GlobalEventRoute.Handlers.Add
97 : (
98 : new RouteHandlerDelegate
99 : {
100 : Delegate = DelegateAdjuster.CastArgument<IMessage, TMessage>(x => handler(x)),
101 : TargetedType = null
102 : }
103 : );
104 : }
105 :
106 : #endregion
107 :
108 : /// <summary>
109 : /// Gets the single <see cref="RouteHandlerDelegate"/> expected for handling <typeparamref name="TMessage"/>.
110 : /// </summary>
111 : /// <typeparam name="TMessage">The <see cref="Type"/> of <see cref="IMessage"/> to find a <see cref="RouteHandlerDelegate"/> for.</typeparam>
112 : /// <param name="throwExceptionOnNoRouteHandlers">If true will throw an <see cref="Exception"/> if no <see cref="RouteHandlerDelegate"/> found.</param>
113 : /// <exception cref="MultipleCommandHandlersRegisteredException">If more than one <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="ICommand{TAuthenticationToken}"/>.</exception>
114 : /// <exception cref="NoCommandHandlerRegisteredException">If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="ICommand{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
115 : /// <exception cref="InvalidOperationException">
116 : /// If more than one <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is not an <see cref="ICommand{TAuthenticationToken}"/> OR
117 : /// If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is not an <see cref="ICommand{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
118 1 : public RouteHandlerDelegate GetSingleHandler<TMessage>(bool throwExceptionOnNoRouteHandlers = true)
119 : where TMessage : IMessage
120 : {
121 : Route route;
122 : Type messageType = typeof(TMessage);
123 : bool isACommand = IsACommand(messageType);
124 :
125 : if (Routes.TryGetValue(typeof(TMessage), out route))
126 : {
127 : if (route.Handlers == null || route.Handlers.Count != 1)
128 : {
129 : if (isACommand)
130 : throw new MultipleCommandHandlersRegisteredException(messageType);
131 : throw new InvalidOperationException("Cannot send to more than one handler.");
132 : }
133 : return route.Handlers.Single();
134 : }
135 :
136 : if (throwExceptionOnNoRouteHandlers)
137 : {
138 : if (isACommand)
139 : throw new NoCommandHandlerRegisteredException(messageType);
140 : throw new InvalidOperationException("No handler registered.");
141 : }
142 :
143 : return null;
144 : }
145 :
146 : /// <summary>
147 : /// Gets the single <see cref="RouteHandlerDelegate"/> expected for handling <typeparamref name="TMessage"/>.
148 : /// </summary>
149 : /// <typeparam name="TMessage">The <see cref="Type"/> of <see cref="IMessage"/> to find a <see cref="RouteHandlerDelegate"/> for.</typeparam>
150 : /// <param name="message">The <typeparamref name="TMessage"/> to find a <see cref="RouteHandlerDelegate"/> for. </param>
151 : /// <param name="throwExceptionOnNoRouteHandlers">If true will throw an <see cref="Exception"/> if no <see cref="RouteHandlerDelegate"/> found.</param>
152 : /// <exception cref="MultipleCommandHandlersRegisteredException">If more than one <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="ICommand{TAuthenticationToken}"/>.</exception>
153 : /// <exception cref="NoCommandHandlerRegisteredException">If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="ICommand{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
154 : /// <exception cref="InvalidOperationException">
155 : /// If more than one <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is not an <see cref="ICommand{TAuthenticationToken}"/> OR
156 : /// If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is not an <see cref="ICommand{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
157 1 : public RouteHandlerDelegate GetSingleHandler<TMessage>(TMessage message, bool throwExceptionOnNoRouteHandlers = true)
158 : where TMessage : IMessage
159 : {
160 : Route route;
161 : Type messageType = message.GetType();
162 : bool isACommand = IsACommand(messageType);
163 :
164 : if (Routes.TryGetValue(messageType, out route))
165 : {
166 : if (route.Handlers != null)
167 : {
168 : if (route.Handlers.Count > 1)
169 : {
170 : if (isACommand)
171 : throw new MultipleCommandHandlersRegisteredException(messageType);
172 : throw new InvalidOperationException("Cannot send to more than one handler.");
173 : }
174 : if (route.Handlers.Count == 1)
175 : return route.Handlers.Single();
176 : }
177 : }
178 :
179 : if (throwExceptionOnNoRouteHandlers)
180 : {
181 : if (isACommand)
182 : throw new NoCommandHandlerRegisteredException(messageType);
183 : throw new InvalidOperationException("No handler registered.");
184 : }
185 :
186 : return null;
187 : }
188 :
189 : /// <summary>
190 : /// Gets the collection <see cref="RouteHandlerDelegate"/> that are expected for handling <typeparamref name="TMessage"/>.
191 : /// </summary>
192 : /// <typeparam name="TMessage">The <see cref="Type"/> of <see cref="IMessage"/> to find a <see cref="RouteHandlerDelegate"/> for.</typeparam>
193 : /// <param name="message">The <typeparamref name="TMessage"/> to find a <see cref="RouteHandlerDelegate"/> for. </param>
194 : /// <param name="throwExceptionOnNoRouteHandlers">If true will throw an <see cref="Exception"/> if no <see cref="RouteHandlerDelegate"/> found.</param>
195 : /// <exception cref="NoCommandHandlerRegisteredException">If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="ICommand{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
196 : /// <exception cref="NoEventHandlerRegisteredException"> If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is an <see cref="IEvent{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
197 : /// <exception cref="NoHandlerRegisteredException"> If no <see cref="RouteHandlerDelegate"/> is found and <typeparamref name="TMessage"/> is not either an <see cref="ICommand{TAuthenticationToken}"/> or an <see cref="IEvent{TAuthenticationToken}"/> and <paramref name="throwExceptionOnNoRouteHandlers"/> is true.</exception>
198 1 : public IEnumerable<RouteHandlerDelegate> GetHandlers<TMessage>(TMessage message, bool throwExceptionOnNoRouteHandlers = true)
199 : where TMessage : IMessage
200 : {
201 : Type messageType = message.GetType();
202 : bool isACommand = IsACommand(messageType);
203 : bool isAnEvent = IsAnEvent(messageType);
204 :
205 : var routeHandlers = new List<RouteHandlerDelegate>();
206 : if (isAnEvent && GlobalEventRoute.Handlers != null)
207 : routeHandlers.AddRange(GlobalEventRoute.Handlers);
208 :
209 : Route route;
210 : if (Routes.TryGetValue(messageType, out route))
211 : routeHandlers.AddRange(route.Handlers);
212 :
213 : if (routeHandlers.Any())
214 : return routeHandlers;
215 :
216 : if (throwExceptionOnNoRouteHandlers)
217 : {
218 : if (isACommand)
219 : throw new NoCommandHandlerRegisteredException(messageType);
220 : if (isAnEvent)
221 : throw new NoEventHandlerRegisteredException(messageType);
222 : throw new NoHandlerRegisteredException(messageType);
223 : }
224 :
225 : return routeHandlers;
226 : }
227 :
228 : /// <summary>
229 : /// Checks if the provided <paramref name="message"/> is an <see cref="ICommand{TAuthenticationToken}"/>.
230 : /// </summary>
231 : /// <typeparam name="TMessage">The <see cref="Type"/> of <see cref="IMessage"/> to check.</typeparam>
232 : /// <param name="message">The <typeparamref name="TMessage"/> to check. </param>
233 : /// <returns>true if <paramref name="message"/> is an <see cref="ICommand{TAuthenticationToken}"/>.</returns>
234 1 : protected virtual bool IsACommand<TMessage>(TMessage message)
235 : {
236 : Type messageType = message.GetType();
237 : return IsACommand(messageType);
238 : }
239 :
240 : /// <summary>
241 : /// Checks if the provided <paramref name="messageType"/> implements <see cref="ICommand{TAuthenticationToken}"/>.
242 : /// </summary>
243 : /// <param name="messageType">The <see cref="Type"/> of object to check.</param>
244 : /// <returns>true if <paramref name="messageType"/> implements <see cref="ICommand{TAuthenticationToken}"/>.</returns>
245 1 : protected virtual bool IsACommand(Type messageType)
246 : {
247 : bool isACommand = false;
248 : Type messageCommandInterface = messageType.GetInterfaces().FirstOrDefault(type => type.FullName.StartsWith(CommandType.FullName));
249 : if (messageCommandInterface != null)
250 : {
251 : Type[] genericArguments = messageCommandInterface.GetGenericArguments();
252 : if (genericArguments.Length == 1)
253 : isACommand = CommandType.MakeGenericType(genericArguments.Single()).IsAssignableFrom(messageType);
254 : }
255 :
256 : return isACommand;
257 : }
258 :
259 : /// <summary>
260 : /// Checks if the provided <paramref name="message"/> is an <see cref="IEvent{TAuthenticationToken}"/>.
261 : /// </summary>
262 : /// <typeparam name="TMessage">The <see cref="Type"/> of <see cref="IMessage"/> to check.</typeparam>
263 : /// <param name="message">The <typeparamref name="TMessage"/> to check. </param>
264 : /// <returns>true if <paramref name="message"/> is an <see cref="IEvent{TAuthenticationToken}"/>.</returns>
265 1 : protected virtual bool IsAnEvent<TMessage>(TMessage message)
266 : {
267 : Type messageType = message.GetType();
268 : return IsAnEvent(messageType);
269 : }
270 :
271 : /// <summary>
272 : /// Checks if the provided <paramref name="messageType"/> implements <see cref="IEvent{TAuthenticationToken}"/>.
273 : /// </summary>
274 : /// <param name="messageType">The <see cref="Type"/> of object to check.</param>
275 : /// <returns>true if <paramref name="messageType"/> implements <see cref="IEvent{TAuthenticationToken}"/>.</returns>
276 1 : protected virtual bool IsAnEvent(Type messageType)
277 : {
278 : bool isAnEvent = false;
279 : Type messageEventInterface = messageType.GetInterfaces().FirstOrDefault(type => type.FullName.StartsWith(EventType.FullName));
280 : if (messageEventInterface != null)
281 : {
282 : Type[] genericArguments = messageEventInterface.GetGenericArguments();
283 : if (genericArguments.Length == 1)
284 : isAnEvent = EventType.MakeGenericType(genericArguments.Single()).IsAssignableFrom(messageType);
285 : }
286 :
287 : return isAnEvent;
288 : }
289 : }
290 : }
|