fixed service registration for event handlers and executors.

This commit is contained in:
kay.one 2013-05-07 22:47:15 -07:00
parent 399c96c5e3
commit fa8f67d7fe
12 changed files with 190 additions and 95 deletions

View File

@ -5,7 +5,7 @@ module.exports = function (grunt) {
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
curl: { curl: {
'UI/JsLibraries/backbone.js' : 'http://backbonejs.org/backbone.js', 'UI/JsLibraries/backbone.js' : 'http://documentcloud.github.io/backbone/backbone.js',
'UI/JsLibraries/backbone.marionette.js' : 'http://marionettejs.com/downloads/backbone.marionette.js', 'UI/JsLibraries/backbone.marionette.js' : 'http://marionettejs.com/downloads/backbone.marionette.js',
'UI/JsLibraries/backbone.modelbinder.js' : 'http://raw.github.com/theironcook/Backbone.ModelBinder/master/Backbone.ModelBinder.js', 'UI/JsLibraries/backbone.modelbinder.js' : 'http://raw.github.com/theironcook/Backbone.ModelBinder/master/Backbone.ModelBinder.js',
'UI/JsLibraries/backbone.mutators.js' : 'http://raw.github.com/asciidisco/Backbone.Mutators/master/backbone.mutators.js', 'UI/JsLibraries/backbone.mutators.js' : 'http://raw.github.com/asciidisco/Backbone.Mutators/master/backbone.mutators.js',

View File

@ -23,13 +23,13 @@ namespace NzbDrone.Api.Commands
{ {
var commandType = _commands.Single(c => c.GetType().Name.Replace("Command", "").Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase)) var commandType = _commands.Single(c => c.GetType().Name.Replace("Command", "").Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase))
.GetType(); .GetType();
var command = (object)Request.Body.FromJson<ICommand>(commandType);
var method = typeof(IMessageAggregator).GetMethod("PublishCommand");
var genericMethod = method.MakeGenericMethod(commandType); var command = Request.Body.FromJson<ICommand>(commandType);
genericMethod.Invoke(_messageAggregator, new[] { command });
_messageAggregator.PublishCommand(command);
return resource; return resource;
} }
} }
} }

View File

@ -1,10 +1,13 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using FluentAssertions; using FluentAssertions;
using TinyIoC;
namespace NzbDrone.App.Test namespace NzbDrone.App.Test
{ {
@ -28,5 +31,24 @@ namespace NzbDrone.App.Test
{ {
MainAppContainerBuilder.BuildContainer().Resolve<IEnumerable<IDownloadClient>>().Should().NotBeEmpty(); MainAppContainerBuilder.BuildContainer().Resolve<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
} }
[Test]
public void container_should_inject_itself()
{
var factory = MainAppContainerBuilder.BuildContainer().Resolve<IServiceFactory>();
factory.Build<IIndexerService>().Should().NotBeNull();
}
[Test]
public void should_resolve_command_executor_by_name()
{
var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand));
var executor = MainAppContainerBuilder.BuildContainer().Resolve(genericExecutor);
executor.Should().NotBeNull();
executor.Should().BeAssignableTo<IExecute<RssSyncCommand>>();
}
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
@ -8,31 +7,35 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.EventingTests namespace NzbDrone.Common.Test.EventingTests
{ {
[TestFixture] [TestFixture]
public class MessageAggregatorCommandTests : TestBase public class MessageAggregatorCommandTests : TestBase<MessageAggregator>
{ {
private Mock<IExecute<CommandA>> _executorA;
private Mock<IExecute<CommandB>> _executorB;
[SetUp]
public void Setup()
{
_executorA = new Mock<IExecute<CommandA>>();
_executorB = new Mock<IExecute<CommandB>>();
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandA>)))
.Returns(_executorA.Object);
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandB>)))
.Returns(_executorB.Object);
}
[Test] [Test]
public void should_publish_command_to_executor() public void should_publish_command_to_executor()
{ {
var commandA = new CommandA(); var commandA = new CommandA();
var executor = new Mock<IExecute<CommandA>>(); Subject.PublishCommand(commandA);
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor.Object });
aggregator.PublishCommand(commandA);
executor.Verify(c => c.Execute(commandA), Times.Once()); _executorA.Verify(c => c.Execute(commandA), Times.Once());
}
[Test]
public void should_throw_if_more_than_one_handler()
{
var commandA = new CommandA();
var executor1 = new Mock<IExecute<CommandA>>();
var executor2 = new Mock<IExecute<CommandA>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor1.Object, executor2.Object });
Assert.Throws<InvalidOperationException>(() => aggregator.PublishCommand(commandA));
} }
[Test] [Test]
@ -40,14 +43,11 @@ namespace NzbDrone.Common.Test.EventingTests
{ {
var commandA = new CommandA(); var commandA = new CommandA();
var executor1 = new Mock<IExecute<CommandA>>();
var executor2 = new Mock<IExecute<CommandB>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor1.Object, executor2.Object });
aggregator.PublishCommand(commandA); Subject.PublishCommand(commandA);
executor1.Verify(c => c.Execute(commandA), Times.Once()); _executorA.Verify(c => c.Execute(commandA), Times.Once());
executor2.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never()); _executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
} }
[Test] [Test]
@ -55,13 +55,10 @@ namespace NzbDrone.Common.Test.EventingTests
{ {
var commandA = new CommandA(); var commandA = new CommandA();
var executor = new Mock<IExecute<CommandA>>(); _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
executor.Setup(c => c.Execute(It.IsAny<CommandA>()))
.Throws(new NotImplementedException()); .Throws(new NotImplementedException());
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor.Object }); Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
Assert.Throws<NotImplementedException>(() => aggregator.PublishCommand(commandA));
} }
} }

View File

@ -8,33 +8,42 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.EventingTests namespace NzbDrone.Common.Test.EventingTests
{ {
[TestFixture] [TestFixture]
public class MessageAggregatorEventTests : TestBase public class MessageAggregatorEventTests : TestBase<MessageAggregator>
{ {
private Mock<IHandle<EventA>> HandlerA1;
private Mock<IHandle<EventA>> HandlerA2;
private Mock<IHandle<EventB>> HandlerB1;
private Mock<IHandle<EventB>> HandlerB2;
[SetUp]
public void Setup()
{
HandlerA1 = new Mock<IHandle<EventA>>();
HandlerA2 = new Mock<IHandle<EventA>>();
HandlerB1 = new Mock<IHandle<EventB>>();
HandlerB2 = new Mock<IHandle<EventB>>();
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.BuildAll<IHandle<EventA>>())
.Returns(new List<IHandle<EventA>> { HandlerA1.Object, HandlerA2.Object });
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.BuildAll<IHandle<EventB>>())
.Returns(new List<IHandle<EventB>> { HandlerB1.Object, HandlerB2.Object });
}
[Test] [Test]
public void should_publish_event_to_handlers() public void should_publish_event_to_handlers()
{ {
var eventA = new EventA(); var eventA = new EventA();
Subject.PublishEvent(eventA);
var intHandler = new Mock<IHandle<EventA>>(); HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { intHandler.Object }); HandlerA2.Verify(c => c.Handle(eventA), Times.Once());
aggregator.PublishEvent(eventA);
intHandler.Verify(c => c.Handle(eventA), Times.Once());
}
[Test]
public void should_publish_to_more_than_one_handler()
{
var eventA = new EventA();
var intHandler1 = new Mock<IHandle<EventA>>();
var intHandler2 = new Mock<IHandle<EventA>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { intHandler1.Object, intHandler2.Object });
aggregator.PublishEvent(eventA);
intHandler1.Verify(c => c.Handle(eventA), Times.Once());
intHandler2.Verify(c => c.Handle(eventA), Times.Once());
} }
[Test] [Test]
@ -42,14 +51,14 @@ namespace NzbDrone.Common.Test.EventingTests
{ {
var eventA = new EventA(); var eventA = new EventA();
var aHandler = new Mock<IHandle<EventA>>();
var bHandler = new Mock<IHandle<EventB>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { aHandler.Object, bHandler.Object });
aggregator.PublishEvent(eventA); Subject.PublishEvent(eventA);
aHandler.Verify(c => c.Handle(eventA), Times.Once()); HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
bHandler.Verify(c => c.Handle(It.IsAny<EventB>()), Times.Never()); HandlerA2.Verify(c => c.Handle(eventA), Times.Once());
HandlerB1.Verify(c => c.Handle(It.IsAny<EventB>()), Times.Never());
HandlerB2.Verify(c => c.Handle(It.IsAny<EventB>()), Times.Never());
} }
@ -58,19 +67,14 @@ namespace NzbDrone.Common.Test.EventingTests
{ {
var eventA = new EventA(); var eventA = new EventA();
var intHandler1 = new Mock<IHandle<EventA>>();
var intHandler2 = new Mock<IHandle<EventA>>();
var intHandler3 = new Mock<IHandle<EventA>>();
intHandler2.Setup(c => c.Handle(It.IsAny<EventA>())) HandlerA1.Setup(c => c.Handle(It.IsAny<EventA>()))
.Throws(new NotImplementedException()); .Throws(new NotImplementedException());
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { intHandler1.Object, intHandler2.Object , intHandler3.Object}); Subject.PublishEvent(eventA);
aggregator.PublishEvent(eventA);
intHandler1.Verify(c => c.Handle(eventA), Times.Once());
intHandler3.Verify(c => c.Handle(eventA), Times.Once());
HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
HandlerA2.Verify(c => c.Handle(eventA), Times.Once());
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
} }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using NzbDrone.Common.Messaging;
using TinyIoC; using TinyIoC;
namespace NzbDrone.Common namespace NzbDrone.Common
@ -27,9 +28,16 @@ namespace NzbDrone.Common
private void AutoRegisterInterfaces() private void AutoRegisterInterfaces()
{ {
var interfaces = _loadedTypes.Where(t => t.IsInterface); var simpleInterfaces = _loadedTypes.Where(t => t.IsInterface).ToList();
var appliedInterfaces = _loadedTypes.SelectMany(t => t.GetInterfaces()).Where(i => i.Assembly.FullName.Contains("NzbDrone")).ToList();
foreach (var contract in interfaces) var contracts = simpleInterfaces.Union(appliedInterfaces)
.Except(new List<Type> { typeof(IMessage), typeof(ICommand), typeof(IEvent) });
var count = contracts.Count();
foreach (var contract in simpleInterfaces.Union(contracts))
{ {
AutoRegisterImplementations(contract); AutoRegisterImplementations(contract);
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Reflection;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
@ -9,24 +8,20 @@ namespace NzbDrone.Common.Messaging
public class MessageAggregator : IMessageAggregator public class MessageAggregator : IMessageAggregator
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly Func<IEnumerable<IProcessMessage>> _handlerFactory; private readonly IServiceFactory _serviceFactory;
public MessageAggregator(Logger logger, Func<IEnumerable<IProcessMessage>> handlers) public MessageAggregator(Logger logger, IServiceFactory serviceFactory)
{ {
_logger = logger; _logger = logger;
_handlerFactory = handlers; _serviceFactory = serviceFactory;
} }
public void PublishEvent<TEvent>(TEvent @event) where TEvent : IEvent public void PublishEvent<TEvent>(TEvent @event) where TEvent : IEvent
{ {
_logger.Trace("Publishing {0}", @event.GetType().Name); _logger.Trace("Publishing {0}", @event.GetType().Name);
var handlers = _handlerFactory().ToList();
//call synchronous handlers first. //call synchronous handlers first.
foreach (var handler in handlers.OfType<IHandle<TEvent>>()) foreach (var handler in _serviceFactory.BuildAll<IHandle<TEvent>>())
{ {
try try
{ {
@ -40,7 +35,7 @@ namespace NzbDrone.Common.Messaging
} }
} }
foreach (var handler in handlers.OfType<IHandleAsync<TEvent>>()) foreach (var handler in _serviceFactory.BuildAll<IHandleAsync<TEvent>>())
{ {
var handlerLocal = handler; var handlerLocal = handler;
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
@ -55,10 +50,27 @@ namespace NzbDrone.Common.Messaging
public void PublishCommand<TCommand>(TCommand command) where TCommand : ICommand public void PublishCommand<TCommand>(TCommand command) where TCommand : ICommand
{ {
var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
_logger.Trace("Publishing {0}", command.GetType().Name); _logger.Trace("Publishing {0}", command.GetType().Name);
var handler = _handlerFactory().OfType<IExecute<TCommand>>().Single();
var handler = _serviceFactory.Build(handlerContract);
_logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name); _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name);
handler.Execute(command);
try
{
handlerContract.GetMethod("Execute").Invoke(handler, new object[] { command });
}
catch (TargetInvocationException e)
{
if (e.InnerException != null)
{
throw e.InnerException;
}
throw;
}
_logger.Debug("{0} <- {1}", command.GetType().Name, handler.GetType().Name); _logger.Debug("{0} <- {1}", command.GetType().Name, handler.GetType().Name);
} }
} }

View File

@ -0,0 +1,17 @@
using System;
namespace NzbDrone.Common.Messaging
{
public static class MessageExtensions
{
public static string GetExecutorName(this Type commandType)
{
if (!typeof(ICommand).IsAssignableFrom(commandType))
{
throw new ArgumentException("commandType must implement IExecute");
}
return string.Format("I{0}Executor", commandType.Name);
}
}
}

View File

@ -116,7 +116,9 @@
<Compile Include="Expansive\TreeNodeList.cs" /> <Compile Include="Expansive\TreeNodeList.cs" />
<Compile Include="IJsonSerializer.cs" /> <Compile Include="IJsonSerializer.cs" />
<Compile Include="Instrumentation\VersionLayoutRenderer.cs" /> <Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
<Compile Include="Messaging\MessageExtensions.cs" />
<Compile Include="Reflection\ReflectionExtensions.cs" /> <Compile Include="Reflection\ReflectionExtensions.cs" />
<Compile Include="ServiceFactory.cs" />
<Compile Include="StringExtention.cs" /> <Compile Include="StringExtention.cs" />
<Compile Include="HttpProvider.cs" /> <Compile Include="HttpProvider.cs" />
<Compile Include="ConfigFileProvider.cs" /> <Compile Include="ConfigFileProvider.cs" />
@ -161,9 +163,6 @@
<ItemGroup> <ItemGroup>
<Content Include="Expansive\license.txt" /> <Content Include="Expansive\license.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" /> <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Collections.Generic;
using TinyIoC;
namespace NzbDrone.Common
{
public interface IServiceFactory
{
T Build<T>() where T : class;
IEnumerable<T> BuildAll<T>() where T : class;
object Build(Type contract);
}
public class ServiceFactory : IServiceFactory
{
private readonly TinyIoCContainer _container;
public ServiceFactory(TinyIoCContainer container)
{
_container = container;
}
public T Build<T>() where T : class
{
return _container.Resolve<T>();
}
public IEnumerable<T> BuildAll<T>() where T : class
{
return _container.ResolveAll<T>();
}
public object Build(Type contract)
{
return _container.Resolve(contract);
}
}
}

View File

@ -1,7 +1,5 @@
using System.IO; using FluentAssertions;
using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Api.RootFolders;
namespace NzbDrone.Integration.Test namespace NzbDrone.Integration.Test
{ {
@ -13,10 +11,8 @@ namespace NzbDrone.Integration.Test
{ {
var indexers = Indexers.All(); var indexers = Indexers.All();
indexers.Should().NotBeEmpty(); indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name)); indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
} }
} }
} }

View File

@ -31,6 +31,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="FluentAssertions"> <Reference Include="FluentAssertions">