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'),
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.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',

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))
.GetType();
var command = (object)Request.Body.FromJson<ICommand>(commandType);
var method = typeof(IMessageAggregator).GetMethod("PublishCommand");
var genericMethod = method.MakeGenericMethod(commandType);
genericMethod.Invoke(_messageAggregator, new[] { command });
var command = Request.Body.FromJson<ICommand>(commandType);
_messageAggregator.PublishCommand(command);
return resource;
}
}
}

View File

@ -1,10 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Test.Common;
using FluentAssertions;
using TinyIoC;
namespace NzbDrone.App.Test
{
@ -28,5 +31,24 @@ namespace NzbDrone.App.Test
{
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.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Messaging;
@ -8,31 +7,35 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.EventingTests
{
[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]
public void should_publish_command_to_executor()
{
var commandA = new CommandA();
var executor = new Mock<IExecute<CommandA>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor.Object });
aggregator.PublishCommand(commandA);
Subject.PublishCommand(commandA);
executor.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));
_executorA.Verify(c => c.Execute(commandA), Times.Once());
}
[Test]
@ -40,14 +43,11 @@ namespace NzbDrone.Common.Test.EventingTests
{
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());
executor2.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
_executorA.Verify(c => c.Execute(commandA), Times.Once());
_executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
}
[Test]
@ -55,13 +55,10 @@ namespace NzbDrone.Common.Test.EventingTests
{
var commandA = new CommandA();
var executor = new Mock<IExecute<CommandA>>();
executor.Setup(c => c.Execute(It.IsAny<CommandA>()))
_executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
.Throws(new NotImplementedException());
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { executor.Object });
Assert.Throws<NotImplementedException>(() => aggregator.PublishCommand(commandA));
Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
}
}

View File

@ -8,33 +8,42 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.EventingTests
{
[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]
public void should_publish_event_to_handlers()
{
var eventA = new EventA();
Subject.PublishEvent(eventA);
var intHandler = new Mock<IHandle<EventA>>();
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { intHandler.Object });
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());
HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
HandlerA2.Verify(c => c.Handle(eventA), Times.Once());
}
[Test]
@ -42,14 +51,14 @@ namespace NzbDrone.Common.Test.EventingTests
{
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());
bHandler.Verify(c => c.Handle(It.IsAny<EventB>()), Times.Never());
HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
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 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());
var aggregator = new MessageAggregator(TestLogger, () => new List<IProcessMessage> { intHandler1.Object, intHandler2.Object , intHandler3.Object});
aggregator.PublishEvent(eventA);
intHandler1.Verify(c => c.Handle(eventA), Times.Once());
intHandler3.Verify(c => c.Handle(eventA), Times.Once());
Subject.PublishEvent(eventA);
HandlerA1.Verify(c => c.Handle(eventA), Times.Once());
HandlerA2.Verify(c => c.Handle(eventA), Times.Once());
ExceptionVerification.ExpectedErrors(1);
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NzbDrone.Common.Messaging;
using TinyIoC;
namespace NzbDrone.Common
@ -27,9 +28,16 @@ namespace NzbDrone.Common
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);
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using NLog;
@ -9,24 +8,20 @@ namespace NzbDrone.Common.Messaging
public class MessageAggregator : IMessageAggregator
{
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;
_handlerFactory = handlers;
_serviceFactory = serviceFactory;
}
public void PublishEvent<TEvent>(TEvent @event) where TEvent : IEvent
{
_logger.Trace("Publishing {0}", @event.GetType().Name);
var handlers = _handlerFactory().ToList();
//call synchronous handlers first.
foreach (var handler in handlers.OfType<IHandle<TEvent>>())
foreach (var handler in _serviceFactory.BuildAll<IHandle<TEvent>>())
{
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;
Task.Factory.StartNew(() =>
@ -55,10 +50,27 @@ namespace NzbDrone.Common.Messaging
public void PublishCommand<TCommand>(TCommand command) where TCommand : ICommand
{
var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
_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);
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);
}
}

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="IJsonSerializer.cs" />
<Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
<Compile Include="Messaging\MessageExtensions.cs" />
<Compile Include="Reflection\ReflectionExtensions.cs" />
<Compile Include="ServiceFactory.cs" />
<Compile Include="StringExtention.cs" />
<Compile Include="HttpProvider.cs" />
<Compile Include="ConfigFileProvider.cs" />
@ -161,9 +163,6 @@
<ItemGroup>
<Content Include="Expansive\license.txt" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- 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 NzbDrone.Api.RootFolders;
namespace NzbDrone.Integration.Test
{
@ -13,10 +11,8 @@ namespace NzbDrone.Integration.Test
{
var indexers = Indexers.All();
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
}
}
}

View File

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