Skip to content

Commit

Permalink
Fixed in memory bus to be thread safe.
Browse files Browse the repository at this point in the history
Added tests for open generics.
  • Loading branch information
Adam Greene committed Apr 21, 2015
1 parent f89f261 commit c72c701
Show file tree
Hide file tree
Showing 34 changed files with 237,298 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,51 @@ public void When_a_multiple_specific_handlers_are_register_they_all_should_be_ca
specificEventHandler2.AssertWasCalled(h => h.Handle(null), options => options.IgnoreArguments().Repeat.Times(2));
specificEventHandler3.AssertWasCalled(h => h.Handle(null), options => options.IgnoreArguments().Repeat.Times(2));
}

[Test]
public void it_should_register_all_concrete_and_open_handlers()
{
var bus = new InProcessEventBus();
bus.RegisterAllHandlersInAssemblyMatching(this.GetType().Assembly, x => x.Namespace.Equals("Ncqrs.Tests.Eventing.ServiceModel.Bus"));
bus.RegisterAllOpenGenericHandlersInAssembly(this.GetType().Assembly, type => Activator.CreateInstance(type));
//bus.RegisterOpenGenericEventsHandler(typeof(OpenEvent<>), typeof(OpenEventHandler<>));
bus.Publish(CreateAEvent(new OpenEvent<string>()));
bus.Publish(CreateAEvent(new StandardEvent()));
Assert.AreEqual(1, openCount);
Assert.AreEqual(1, standardCount);
}

private static IPublishableEvent CreateAEvent(object evnt)
{
return new UncommittedEvent(Guid.NewGuid(), Guid.NewGuid(), 0, 0, DateTime.UtcNow, evnt, new Version(1, 0));
}

private static int openCount = 0;
private static int standardCount = 0;

public class StandardEvent
{
}


public class StandardEventHandler : IEventHandler<StandardEvent>
{
public void Handle(IPublishedEvent<StandardEvent> evnt)
{
standardCount++;
}
}

public class OpenEvent<T>
{
}

public class OpenEventHandler<T> : IEventHandler<OpenEvent<T>>
{
public void Handle(IPublishedEvent<OpenEvent<T>> evnt)
{
openCount++;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,55 @@ public static void RegisterOpenGenericEventsHandler(this InProcessEventBus bus,
bus.RegisterHandler(eventType, evnt => Handle(handlerType, evnt, handlerFactory));
}

public static void RegisterAllOpenGenericHandlersInAssembly(this InProcessEventBus bus, Assembly asm, Func<Type, object> handlerFactory)
{
RegisterAllOpenGenericHandlersInAssembly(bus, x => true, asm, handlerFactory);
}

public static void RegisterAllOpenGenericHandlersInAssembly(this InProcessEventBus bus, Func<Type, bool> where, Assembly asm, Func<Type, object> handlerFactory)
{
foreach (var type in asm.GetTypes().Where(x => ImplementsAtLeastOneIEventHandlerInterface(x) && where(x)))
{
var interfaces = type.GetInterfaces();
foreach (var handlerInterfaceType in interfaces.Where(x => IsIEventHandlerInterface(x) && IsGenericDefinition(x)))
{
var eventDataType = handlerInterfaceType.GetGenericArguments().First().GetGenericTypeDefinition();
RegisterOpenGenericEventsHandler(bus, eventDataType, type, handlerFactory);
}

foreach (var handlerInterfaceType in interfaces.Where(x => IsIEventHandlerInterface(x) && IsConcrete(x)))
{
var eventDataType = handlerInterfaceType.GetGenericArguments().First();
throw new InvalidOperationException(string.Format("Cannot define concrete event handlers in an open generics class ({0} in {1})", eventDataType, type));
}
}
}

private static bool IsConcrete(Type x)
{
return !x.GetGenericArguments()[0].IsGenericType;
}

private static bool IsGenericDefinition(Type x)
{
return x.GetGenericArguments()[0].IsGenericType;

}

private static bool ImplementsAtLeastOneIEventHandlerInterface(Type type)
{
return type.IsClass && !type.IsAbstract &&
type.GetInterfaces().Any(IsIEventHandlerInterface)
&& type.IsGenericTypeDefinition;
}

private static bool IsIEventHandlerInterface(Type type)
{
return type.IsInterface &&
type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(IEventHandler<>);
}

private static Dictionary<Type, Action<PublishedEvent>> handlerCache = new Dictionary<Type, Action<PublishedEvent>>();

private static void Handle(Type handlerRootType, PublishedEvent evnt, Func<Type, Object> handlerFactory)
Expand Down Expand Up @@ -74,5 +123,10 @@ private static object CreateHandler(Type handler)
{
return Activator.CreateInstance(handler);
}

public static void ClearCachedHandlers()
{
handlerCache.Clear();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static void RegisterAllHandlersInAssembly(this InProcessEventBus target,
/// <param name="handlerFactory">The function used to instantiate the given handler type</param>
public static void RegisterAllHandlersInAssembly(this InProcessEventBus target, Assembly asm, Func<Type, object> handlerFactory)
{
foreach(var type in asm.GetTypes().Where(ImplementsAtLeastOneIEventHandlerInterface))
foreach (var type in asm.GetTypes().Where(x => ImplementsAtLeastOneIEventHandlerInterface(x) && IsConcrete(x)))
{
var handler = handlerFactory(type);

Expand Down Expand Up @@ -61,7 +61,7 @@ public static void RegisterAllHandlersInAssemblyMatching(this InProcessEventBus
/// <param name="handlerFactory">The function used to instantiate the given handler type</param>
public static void RegisterAllHandlersInAssemblyMatching(this InProcessEventBus target, Assembly asm, Func<Type, bool> matching, Func<Type, object> handlerFactory)
{
foreach (var type in asm.GetTypes().Where(x => ImplementsAtLeastOneIEventHandlerInterface(x) && matching(x)))
foreach (var type in asm.GetTypes().Where(x => matching(x) && ImplementsAtLeastOneIEventHandlerInterface(x) && IsConcrete(x)))
{
var handler = handlerFactory(type);

Expand All @@ -73,6 +73,11 @@ public static void RegisterAllHandlersInAssemblyMatching(this InProcessEventBus
}
}

private static bool IsConcrete(Type x)
{
return !x.ContainsGenericParameters;
}

private static object CreateInstance(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
Expand Down Expand Up @@ -100,9 +105,15 @@ private static bool ImplementsAtLeastOneIEventHandlerInterface(Type type)

private static bool IsIEventHandlerInterface(Type type)
{
return type.IsInterface &&
if(type.IsInterface &&
type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof (IEventHandler<>);
type.GetGenericTypeDefinition() == typeof(IEventHandler<>))
{
var parm = type.GetGenericArguments()[0];
return !parm.IsGenericType;
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class ConventionBasedEventHandlerMappingStrategy : IEventHandlerMappingSt
public Type EventBaseType
{
get { return _eventBaseType; }
set {
set
{
lock (typeMatchedMethods)
{
_eventBaseType = value;
Expand Down Expand Up @@ -67,30 +68,33 @@ public IEnumerable<ISourcedEventHandler> GetEventHandlers(object target)
var targetType = target.GetType();
var handlers = new List<ISourcedEventHandler>();

if (!typeMatchedMethods.ContainsKey(targetType))
lock (typeMatchedMethods)
{
if (!typeMatchedMethods.ContainsKey(targetType))
{

Logger.DebugFormat("Trying to get all event handlers based by convention for {0}.", targetType);

var methodsToMatch = targetType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

var matchedMethods = (from method in methodsToMatch
let parameters = method.GetParameters()
let noEventHandlerAttributes =
method.GetCustomAttributes(typeof(NoEventHandlerAttribute), true)
where
// Get only methods where the name matches.
Regex.IsMatch(method.Name, MethodNameRegexPattern, RegexOptions.CultureInvariant) &&
// Get only methods that have 1 parameter.
parameters.Length == 1 &&
// Get only methods where the first parameter is an event.
EventBaseType.IsAssignableFrom(parameters[0].ParameterType) &&
// Get only methods that are not marked with the no event handler attribute.
noEventHandlerAttributes.Length == 0
select
new MatchedMethods { MethodInfo = method, FirstParameter = method.GetParameters()[0] }).ToList();

typeMatchedMethods.Add(targetType, matchedMethods);
Logger.DebugFormat("Trying to get all event handlers based by convention for {0}.", targetType);

var methodsToMatch = targetType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

var matchedMethods = (from method in methodsToMatch
let parameters = method.GetParameters()
let noEventHandlerAttributes =
method.GetCustomAttributes(typeof(NoEventHandlerAttribute), true)
where
// Get only methods where the name matches.
Regex.IsMatch(method.Name, MethodNameRegexPattern, RegexOptions.CultureInvariant) &&
// Get only methods that have 1 parameter.
parameters.Length == 1 &&
// Get only methods where the first parameter is an event.
EventBaseType.IsAssignableFrom(parameters[0].ParameterType) &&
// Get only methods that are not marked with the no event handler attribute.
noEventHandlerAttributes.Length == 0
select
new MatchedMethods { MethodInfo = method, FirstParameter = method.GetParameters()[0] }).ToList();

typeMatchedMethods.Add(targetType, matchedMethods);
}
}

foreach (var method in typeMatchedMethods[targetType])
Expand All @@ -106,21 +110,17 @@ public IEnumerable<ISourcedEventHandler> GetEventHandlers(object target)
{
var key = Tuple.Create((MethodBase)methodCopy, e.GetType());
var tempCopy = methodCopy;
if (cachedEventHandlerMethods.ContainsKey(key))
{
tempCopy = cachedEventHandlerMethods[key];
}
else
lock (cachedEventHandlerMethods)
{
var arguments = tempCopy.GetGenericArguments();
var eventType = e.GetType();
if (arguments.Length == 1)
if (cachedEventHandlerMethods.ContainsKey(key))
{
if (arguments[0].BaseType.IsAssignableFrom(eventType))
{
tempCopy = tempCopy.MakeGenericMethod(eventType);
}
else
tempCopy = cachedEventHandlerMethods[key];
}
else
{
var arguments = tempCopy.GetGenericArguments();
var eventType = e.GetType();
if (arguments.Length == 1)
{
var eventArgs = eventType.GetGenericArguments();
foreach (var eventArg in eventArgs)
Expand All @@ -132,8 +132,8 @@ public IEnumerable<ISourcedEventHandler> GetEventHandlers(object target)
}
}
}
cachedEventHandlerMethods[key] = tempCopy;
}
cachedEventHandlerMethods[key] = tempCopy;
}

tempCopy.Invoke(target, new[] { e });
Expand Down
5 changes: 4 additions & 1 deletion Framework/src/Ncqrs/Eventing/Storage/InMemoryEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ public void Store(UncommittedEventStream eventStream)
if (!_events.TryGetValue(eventStream.SourceId, out events))
{
events = new Queue<CommittedEvent>();
_events.Add(eventStream.SourceId, events);
lock (_events)
{
_events.Add(eventStream.SourceId, events);
}
}

foreach (var evnt in eventStream)
Expand Down
6 changes: 4 additions & 2 deletions Framework/src/Ncqrs/Ncqrs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net">
<HintPath>..\..\..\lib\ThirdParty\log4net\log4net.dll</HintPath>
<Private>False</Private>
<HintPath>..\..\..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Newtonsoft.Json">
Expand Down Expand Up @@ -287,6 +286,9 @@
<CustomToolNamespace>Ncqrs.Eventing.Storage.SQL.SimpleMicrosoftSqlServerEventStore</CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
Expand Down
4 changes: 4 additions & 0 deletions Framework/src/Ncqrs/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net40" />
</packages>
1 change: 1 addition & 0 deletions NoAzure.playlist

Large diffs are not rendered by default.

Loading

0 comments on commit c72c701

Please sign in to comment.