Add ability to run commands from CommandHostAgent

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-04-08 19:54:41 -07:00
parent f766320b79
commit 343145933c
14 changed files with 246 additions and 78 deletions

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Orchard.Commands;
namespace Orchard.Tests.Commands {
[TestFixture]
public class CommandHandlerDescriptorBuilderTests {
[Test]
public void BuilderShouldCreateDescriptor() {
var builder = new CommandHandlerDescriptorBuilder();
var descriptor = builder.Build(typeof(MyCommand));
Assert.That(descriptor, Is.Not.Null);
Assert.That(descriptor.Commands.Count(), Is.EqualTo(4));
Assert.That(descriptor.Commands.Single(d => d.Name == "FooBar"), Is.Not.Null);
Assert.That(descriptor.Commands.Single(d => d.Name == "FooBar").MethodInfo, Is.EqualTo(typeof(MyCommand).GetMethod("FooBar")));
Assert.That(descriptor.Commands.Single(d => d.Name == "Whereslou"), Is.Not.Null);
Assert.That(descriptor.Commands.Single(d => d.Name == "Whereslou").MethodInfo, Is.EqualTo(typeof(MyCommand).GetMethod("FooBar2")));
Assert.That(descriptor.Commands.Single(d => d.Name == "Foo Bar"), Is.Not.Null);
Assert.That(descriptor.Commands.Single(d => d.Name == "Foo Bar").MethodInfo, Is.EqualTo(typeof(MyCommand).GetMethod("Foo_Bar")));
Assert.That(descriptor.Commands.Single(d => d.Name == "Foo_Bar"), Is.Not.Null);
Assert.That(descriptor.Commands.Single(d => d.Name == "Foo_Bar").MethodInfo, Is.EqualTo(typeof(MyCommand).GetMethod("Foo_Bar3")));
}
public class MyCommand : DefaultOrchardCommandHandler {
public void FooBar() {
}
[OrchardCommand("Whereslou")]
public void FooBar2() {
}
public void Foo_Bar() {
}
[OrchardCommand("Foo_Bar")]
public void Foo_Bar3() {
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections.Specialized;
using NUnit.Framework;
using Orchard.Commands;
using System;
@@ -35,7 +36,7 @@ namespace Orchard.Tests.Commands {
[Test]
public void TestBooleanSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new NameValueCollection() };
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("Verbose", "true");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : This was a test"));
@@ -43,7 +44,7 @@ namespace Orchard.Tests.Commands {
[Test]
public void TestIntSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new NameValueCollection() };
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("Level", "2");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : Entering Level 2"));
@@ -51,7 +52,7 @@ namespace Orchard.Tests.Commands {
[Test]
public void TestStringSwitchForCommand() {
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new NameValueCollection() };
CommandContext commandContext = new CommandContext { Command = "Baz", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("User", "OrchardUser");
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo("Command Baz Called : current user is OrchardUser"));
@@ -59,7 +60,7 @@ namespace Orchard.Tests.Commands {
[Test]
public void TestSwitchForCommandWithoutSupportForIt() {
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new NameValueCollection() };
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("User", "OrchardUser");
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
@@ -73,7 +74,7 @@ namespace Orchard.Tests.Commands {
[Test]
public void TestNotExistingSwitch() {
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new NameValueCollection() };
CommandContext commandContext = new CommandContext { Command = "Foo", Switches = new Dictionary<string, string>() };
commandContext.Switches.Add("ThisSwitchDoesNotExist", "Insignificant");
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}
@@ -82,7 +83,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandArgumentsArePassedCorrectly() {
CommandContext commandContext = new CommandContext {
Command = "Concat",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
Arguments = new[] {"left to ", "right"}
};
_handler.Execute(commandContext);
@@ -93,7 +94,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandArgumentsArePassedCorrectlyWithAParamsParameters() {
CommandContext commandContext = new CommandContext {
Command = "ConcatParams",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
Arguments = new[] {"left to ", "right"}
};
_handler.Execute(commandContext);
@@ -104,7 +105,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandArgumentsArePassedCorrectlyWithAParamsParameterAndNoArguments() {
CommandContext commandContext = new CommandContext {
Command = "ConcatParams",
Switches = new NameValueCollection()
Switches = new Dictionary<string, string>()
};
_handler.Execute(commandContext);
Assert.That(commandContext.Output, Is.EqualTo(""));
@@ -115,7 +116,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandArgumentsArePassedCorrectlyWithNormalParametersAndAParamsParameters() {
CommandContext commandContext = new CommandContext {
Command = "ConcatAllParams",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left-", "center-", "right" }
};
_handler.Execute(commandContext);
@@ -126,7 +127,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandParamsMismatchWithoutParamsNotEnoughArguments() {
CommandContext commandContext = new CommandContext {
Command = "Concat",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left to "}
};
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
@@ -136,7 +137,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandParamsMismatchWithoutParamsTooManyArguments() {
CommandContext commandContext = new CommandContext {
Command = "Foo",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
Arguments = new[] { "left to " }
};
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
@@ -146,7 +147,7 @@ namespace Orchard.Tests.Commands {
public void TestCommandParamsMismatchWithParamsButNotEnoughArguments() {
CommandContext commandContext = new CommandContext {
Command = "ConcatAllParams",
Switches = new NameValueCollection(),
Switches = new Dictionary<string, string>(),
};
Assert.Throws<InvalidOperationException>(() => _handler.Execute(commandContext));
}

View File

@@ -15,6 +15,8 @@ namespace Orchard.Tests.Commands {
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<CommandManager>().As<ICommandManager>();
builder.RegisterType<MyCommand>().As<ICommandHandler>();
builder.RegisterModule(new CommandModule());
var container = builder.Build();
_manager = container.Resolve<ICommandManager>();
@@ -22,7 +24,27 @@ namespace Orchard.Tests.Commands {
[Test]
public void ManagerCanRunACommand() {
_manager.Execute(new CommandContext { Command = "foo bar" });
var context = new CommandParameters { Arguments = new string[] { "FooBar" } };
_manager.Execute(context);
Assert.That(context.Output, Is.EqualTo("success!"));
}
[Test]
public void ManagerCanRunACompositeCommand() {
var context = new CommandParameters { Arguments = ("Foo Bar Bleah").Split(' ') };
_manager.Execute(context);
Assert.That(context.Output, Is.EqualTo("Bleah"));
}
public class MyCommand : DefaultOrchardCommandHandler {
public string FooBar() {
return "success!";
}
public string Foo_Bar(string bleah) {
return bleah;
}
}
}
}

View File

@@ -115,6 +115,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\CommandHandlerDescriptorBuilderTests.cs" />
<Compile Include="Commands\CommandHandlerTests.cs" />
<Compile Include="Commands\CommandManagerTests.cs" />
<Compile Include="ContentManagement\ContentQueryTests.cs">

View File

@@ -1,11 +1,14 @@
using System.Collections.Specialized;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Orchard.Commands {
public class CommandContext {
public string Input { get; set; }
public string Output { get; set; }
public string Command { get; set; }
public string[] Arguments { get; set; }
public NameValueCollection Switches { get; set; }
public IEnumerable<string> Arguments { get; set; }
public IDictionary<string,string> Switches { get; set; }
public CommandDescriptor CommandDescriptor { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Reflection;
namespace Orchard.Commands {
public class CommandDescriptor {
public string Name { get; set; }
public MethodInfo MethodInfo { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Orchard.Commands {
public class CommandHandlerDescriptor {
public IEnumerable<CommandDescriptor> Commands { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Orchard.Commands {
public class CommandHandlerDescriptorBuilder {
public CommandHandlerDescriptor Build(Type type) {
var methods = CollectMethods(type);
return new CommandHandlerDescriptor { Commands = methods };
}
private IEnumerable<CommandDescriptor> CollectMethods(Type type) {
foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) {
var name = GetCommandName(methodInfo);
yield return new CommandDescriptor { Name = name, MethodInfo = methodInfo };
}
}
private string GetCommandName(MethodInfo methodInfo) {
var attributes = methodInfo.GetCustomAttributes(typeof(OrchardCommandAttribute), false/*inherit*/);
if (attributes != null && attributes.Any()) {
return attributes.Cast<OrchardCommandAttribute>().Single().Command;
}
return methodInfo.Name.Replace('_', ' ');
}
}
}

View File

@@ -15,21 +15,20 @@ namespace Orchard.Commands {
/// executing a single command.
/// </summary>
public class CommandHostAgent {
public void RunSingleCommand(string[] args) {
public void RunSingleCommand(string tenant, string[] args, Dictionary<string,string> switches) {
try {
var context = ParseArguments(args);
var hostContainer = OrchardStarter.CreateHostContainer(MvcSingletons);
var host = hostContainer.Resolve<IOrchardHost>();
var tenantManager = hostContainer.Resolve<ITenantManager>();
host.Initialize();
// Find the shell settings for the tenant...
tenant = tenant ?? "default";
// cretae the stand-alone env
// resolve a command
var tenantSettings = tenantManager.LoadSettings().Single(s => String.Equals(s.Name, tenant, StringComparison.OrdinalIgnoreCase));
using (var env = host.CreateStandaloneEnvironment(tenantSettings)) {
env.Resolve<ICommandManager>().Execute(new CommandParameters {Arguments = args, Switches = switches});
}
}
catch (Exception e) {
for(; e != null; e = e.InnerException) {
@@ -38,29 +37,6 @@ namespace Orchard.Commands {
}
}
private static CommandContext ParseArguments(IEnumerable<string> args) {
var arguments = new List<string>();
var switches = new NameValueCollection();
foreach (string arg in args) {
if (arg[0] == '/') {
string[] split = arg.Substring(1).Split(':');
switches.Add(split[0], split.Length >= 2 ? split[1] : string.Empty);
}
else {
arguments.Add(arg);
}
}
return new CommandContext {
Input = "",
Output = "",
Command = arguments[0],
Arguments = arguments.Skip(1).ToArray(),
Switches = switches
};
}
protected void MvcSingletons(ContainerBuilder builder) {
builder.RegisterInstance(ControllerBuilder.Current);
builder.RegisterInstance(RouteTable.Routes);

View File

@@ -5,18 +5,64 @@ using System.Text;
using Autofac.Features.Metadata;
namespace Orchard.Commands {
public interface ICommandManager : IDependency{
void Execute(CommandContext context);
public interface ICommandManager : IDependency {
void Execute(CommandParameters parameters);
}
public class CommandManager : ICommandManager {
private readonly IEnumerable<Meta<ICommandHandler>> _handlers;
private readonly IEnumerable<Meta<Func<ICommandHandler>>> _handlers;
public CommandManager(IEnumerable<Meta<ICommandHandler>> handlers) {
public CommandManager(IEnumerable<Meta<Func<ICommandHandler>>> handlers) {
_handlers = handlers;
}
public void Execute(CommandContext context) {
public void Execute(CommandParameters parameters) {
var matches = _handlers.SelectMany(h => MatchCommands(parameters, GetDescriptor(h.Metadata), h.Value));
if (matches.Count() == 1) {
var match = matches.Single();
match.CommandHandlerFactory().Execute(match.Context);
parameters.Output = match.Context.Output;
}
else if (matches.Any()) {
// too many
}
else {
// none
}
}
private class Match {
public CommandContext Context { get; set; }
public Func<ICommandHandler> CommandHandlerFactory { get; set; }
}
private static IEnumerable<Match> MatchCommands(CommandParameters parameters, CommandHandlerDescriptor descriptor, Func<ICommandHandler> handlerFactory) {
foreach (var commandDescriptor in descriptor.Commands) {
string[] names = commandDescriptor.Name.Split(' ');
if (!parameters.Arguments.Take(names.Count()).SequenceEqual(names, StringComparer.OrdinalIgnoreCase)) {
// leading arguments not equal to command name
continue;
}
yield return new Match {
Context = new CommandContext {
Arguments = parameters.Arguments.Skip(names.Count()),
Command = string.Join(" ",names),
CommandDescriptor = commandDescriptor,
Input = parameters.Input,
Output = parameters.Output,
Switches = parameters.Switches,
},
CommandHandlerFactory = handlerFactory
};
}
}
private static CommandHandlerDescriptor GetDescriptor(IDictionary<string, object> metadata) {
return ((CommandHandlerDescriptor)metadata[typeof(CommandHandlerDescriptor).FullName]);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autofac;
using Autofac.Core;
namespace Orchard.Commands {
public class CommandModule : Module {
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) {
if (!registration.Services.Contains(new TypedService(typeof(ICommandHandler))))
return;
var builder = new CommandHandlerDescriptorBuilder();
var descriptor = builder.Build(registration.Activator.LimitType);
registration.Metadata.Add(typeof(CommandHandlerDescriptor).FullName, descriptor);
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Orchard.Commands {
public class CommandParameters {
public IEnumerable<string> Arguments { get; set; }
public IDictionary<string, string> Switches { get; set; }
public string Input { get; set; }
public string Output { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using Orchard.Localization;
@@ -16,7 +17,7 @@ namespace Orchard.Commands {
public void Execute(CommandContext context) {
if (context.Switches != null && context.Switches.Count > 0) {
foreach (var commandSwitch in context.Switches.AllKeys) {
foreach (var commandSwitch in context.Switches.Keys) {
PropertyInfo propertyInfo = GetType().GetProperty(commandSwitch);
if (propertyInfo == null) {
throw new InvalidOperationException(T("Switch : ") + commandSwitch + T(" was not found"));
@@ -45,32 +46,19 @@ namespace Orchard.Commands {
}
}
foreach (MethodInfo methodInfo in GetType().GetMethods()) {
if (String.Equals(methodInfo.Name, context.Command, StringComparison.OrdinalIgnoreCase)) {
if (TryInvokeCommand(methodInfo, context)) return;
}
foreach (OrchardCommandAttribute commandAttribute in methodInfo.GetCustomAttributes(typeof(OrchardCommandAttribute), false)) {
if (String.Equals(commandAttribute.Command, context.Command, StringComparison.OrdinalIgnoreCase)) {
if (TryInvokeCommand(methodInfo, context)) return;
}
}
}
throw new InvalidOperationException(T("Command : ") + context.Command + T(" was not found "));
InvokeCommand(context);
}
private bool TryInvokeCommand(MethodInfo methodInfo, CommandContext context) {
CheckMethodForSwitches(methodInfo, context.Switches);
object[] invokeParameters = GetInvokeParametersForMethod(methodInfo, context.Arguments ?? new string[] { });
if (invokeParameters != null) {
context.Output = (string)methodInfo.Invoke(this, invokeParameters);
return true;
private void InvokeCommand(CommandContext context) {
CheckMethodForSwitches(context.CommandDescriptor.MethodInfo, context.Switches);
object[] invokeParameters = GetInvokeParametersForMethod(context.CommandDescriptor.MethodInfo, context.Arguments.ToArray() ?? new string[] { });
if (invokeParameters == null) {
throw new ArgumentException(T("Command arguments don't match").ToString());
}
return false;
context.Output = (string)context.CommandDescriptor.MethodInfo.Invoke(this, invokeParameters);
}
private static object[] GetInvokeParametersForMethod(MethodInfo methodInfo, string[] arguments) {
private static object[] GetInvokeParametersForMethod(MethodInfo methodInfo, IList<string> arguments) {
List<object> invokeParameters = new List<object>();
List<string> args = new List<string>(arguments);
@@ -102,14 +90,14 @@ namespace Orchard.Commands {
return invokeParameters.ToArray();
}
private void CheckMethodForSwitches(MethodInfo methodInfo, NameValueCollection switches) {
if (switches == null || switches.AllKeys.Length == 0)
private void CheckMethodForSwitches(MethodInfo methodInfo, IDictionary<string,string> switches) {
if (switches == null || switches.Count == 0)
return;
List<string> supportedSwitches = new List<string>();
var supportedSwitches = new List<string>();
foreach (OrchardSwitchesAttribute switchesAttribute in methodInfo.GetCustomAttributes(typeof(OrchardSwitchesAttribute), false)) {
supportedSwitches.AddRange(switchesAttribute.SwitchName);
}
foreach (var commandSwitch in switches.AllKeys) {
foreach (var commandSwitch in switches.Keys) {
if (!supportedSwitches.Contains(commandSwitch)) {
throw new InvalidOperationException(T("Method ") + methodInfo.Name +
T(" does not support switch ") + commandSwitch);

View File

@@ -137,8 +137,13 @@
<Compile Include="Validation\Argument.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\CommandParameters.cs" />
<Compile Include="Commands\CommandDescriptor.cs" />
<Compile Include="Commands\CommandHandlerDescriptor.cs" />
<Compile Include="Commands\CommandHandlerDescriptorBuilder.cs" />
<Compile Include="Commands\CommandHostAgent.cs" />
<Compile Include="Commands\CommandManager.cs" />
<Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\OrchardCommandAttribute.cs" />
<Compile Include="Commands\CommandContext.cs" />
<Compile Include="Commands\DefaultOrchardCommandHandler.cs" />