예제 #1
0
        /// <summary>
        /// Executes a command asynchronously
        /// </summary>
        /// <param name="sessionID">The ID of the session running the command</param>
        /// <param name="inputCommand">The full command input</param>
        /// <returns>A <see cref="CommandExecutionResponse"/> containing the results of the execution</returns>
        protected async Task <CommandExecutionResponse> ExecuteCommandAsync(string sessionID, string inputCommand)
        {
            try
            {
                // Notify CommandReceived event
                CommandReceived?.Invoke(this, new CommandReceivedEventArgs(inputCommand, sessionID));

                // Parse Full Command, by separating what is the command from what are the arguments
                var ResultingCommand = this.CommandParser.Parse(inputCommand);

                // Try to assign each parameter
                var methodArgumentsList = new List <object>();

                // If arguments are not specified, set an empty array to it
                if (ResultingCommand.Arguments == null)
                {
                    ResultingCommand.Arguments = new ParsedCommandArgument[] { }
                }
                ;

                // Look for command
                if (CommandCollectionInternal.TryGetValue(ResultingCommand.Command, out var commandInfo))
                {
                    // Check for Help Call
                    if (ResultingCommand.IsHelpRequest)
                    {
                        // Display the command help
                        var commandHelpAttribute = commandInfo.GetCommandHelpAttribute();
                        if (commandHelpAttribute == null)
                        {
                            return(CommandExecutionResponse.Help(""));
                        }

                        return(CommandExecutionResponse.Help(commandHelpAttribute.Contents));
                    }
                    else
                    {
                        // Check if required parameters have been informed, if they were not, display help (if available)

                        // Assign Parameters
                        if (ResultingCommand.IsArgumentParameterSpecific)
                        {
                            // The arguments have a parameter name or a identifier, set them in sequence to be used later
                            var currentArgumentsDictionary = new Dictionary <string, ParsedCommandArgument>();
                            foreach (var r in ResultingCommand.Arguments)
                            {
                                if (currentArgumentsDictionary.ContainsKey(r.Parameter.ToLower()))
                                {
                                    throw new Exception($"Parameter '{r.Parameter.ToLower()}' is repeated on the syntax");
                                }

                                currentArgumentsDictionary.Add(r.Parameter.ToLower(), r);
                            }

                            var newArgumentsList = new List <ParsedCommandArgument>();

                            foreach (var p in commandInfo.ParametersInfo)
                            {
                                if (currentArgumentsDictionary.TryGetValue(p.Name.ToLower(), out var parsedCommandArgument))
                                {
                                    throw new Exception($"Parameter '{p.Name.ToLower()}' is not informed");
                                }

                                newArgumentsList.Add(parsedCommandArgument);
                            }

                            ResultingCommand.Arguments = newArgumentsList.ToArray();
                        }

                        // Make sure the number of arguments match the method argument number
                        if (ResultingCommand.Arguments.Length == commandInfo.ParametersInfo.Length)
                        {
                            for (int iArgument = 0; iArgument < ResultingCommand.Arguments.Length; iArgument++)
                            {
                                // Get Variables
                                var param    = commandInfo.ParametersInfo[iArgument];
                                var argument = ResultingCommand.Arguments[iArgument];

                                // Try to apply parameter
                                try
                                {
                                    if (param.ParameterType == typeof(string))
                                    {
                                        // A direct conversion is available
                                        methodArgumentsList.Add(argument.Value);
                                    }
                                    else
                                    {
                                        // There is no direct conversion, try to apply the parameter using the Convert object
                                        methodArgumentsList.Add(Convert.ChangeType(argument, param.ParameterType));
                                    }
                                }
                                catch (Exception eAssignment)
                                {
                                    // Throw an error with the invalid argument
                                    throw new InvalidCommandArgumentsException(ResultingCommand.Command, param.Name, param.ParameterType, argument.Value, eAssignment);
                                }
                            }
                        }
                        else
                        {
                            // Number of arguments do not match, return help
                            var argumentException = new ArgumentException("The number of arguments needed for the command does not match the number of arguments provided");

                            var commandHelpAttribute = commandInfo.GetCommandHelpAttribute();
                            if (commandHelpAttribute == null)
                            {
                                return(CommandExecutionResponse.Help("", DefaultErrorStatus, argumentException));
                            }

                            return(CommandExecutionResponse.Help(commandHelpAttribute.Contents, DefaultErrorStatus, argumentException));
                        }

                        // Arguments are set

                        // Define the CommandClass where the command will be executed against
                        var commandObject = (object)null;

                        // Look for Session Information to start fetching / creating objects
                        if (SessionCollectionInternal.TryGetValue(sessionID, out SessionInfo sessionInfo))
                        {
                            // Session information found

                            // Retrieve command class info
                            if (CommandClassCollectionInternal.TryGetValue(commandInfo.CommandClassType, out var commandClassInfo))
                            {
                                // Check the CommandClass lifetime before trying to find the object where to run the command against
                                if (commandClassInfo.CommandClassAttribute.Lifetime == InstanceLifetimes.Singleton)
                                {
                                    // Singleton lifetime, only one instance of the object should exist

                                    // Make sure to lock the dictionary to force other threads to hold
                                    lock (SingletonObjectsCollectionInternalLock)
                                    {
                                        // Singleton - Object should be on the SingletonObjectCollectionInternal
                                        if (!(SingletonObjectsCollectionInternal.TryGetValue(commandInfo.CommandClassType, out commandObject)))
                                        {
                                            // Singleton object could not be found, then create it and add back to the collection

                                            // Create the Command Object
                                            commandObject = commandClassInfo.CreateInstance();

                                            // Adds the singleton object to the collection
                                            SingletonObjectsCollectionInternal.Add(commandInfo.CommandClassType, commandObject);
                                        }
                                    }
                                }
                                else if (commandClassInfo.CommandClassAttribute.Lifetime == InstanceLifetimes.Scoped)
                                {
                                    // Scoped lifetime, only one instance per session should exist

                                    // Check object on the Session Info
                                    if (!sessionInfo.ObjectCollection.TryGetValue(commandInfo.CommandClassType, out commandObject))
                                    {
                                        // Create and add object to the session
                                        lock (SessionCollectionInternalLock)
                                        {
                                            // Create the Command Object and add to collection
                                            commandObject = commandClassInfo.CreateInstance();
                                            sessionInfo.ObjectCollection.Add(commandInfo.CommandClassType, commandObject);
                                        }
                                    }
                                }
                                else
                                {
                                    // Transient lifetime, initializes it everytime
                                    commandObject = commandClassInfo.CreateInstance();
                                }
                            }
                            else
                            {
                                // Cannot find registration details for command class
                                throw new Exception($"Unable to find registration details for type '{commandInfo.CommandClassType.Name}'");
                            }
                        }
                        else
                        {
                            // Error, unable to find the session
                            throw new Exception($"Session {sessionID} cannot be found");
                        }

                        try
                        {
                            // Calls the Command Asynchronously
                            var result = await Task.Factory.StartNew <object>(() =>
                            {
                                return(commandInfo.MethodInfo.Invoke(commandObject, methodArgumentsList.ToArray()));
                            }
                                                                              ).ConfigureAwait(false);

                            // Depending on the type of result, throw response
                            if (result is CommandExecutionResponse cr)
                            {
                                return(cr);
                            }
                            else
                            {
                                // Command executed without exceptions, create a Success Response with no data
                                return(CommandExecutionResponse.Success(DefaultSuccessStatus, result));
                            }
                        }
                        catch (Exception eExecution)
                        {
                            // Throws the execution exception ahead
                            throw new CommandInExecutionException(ResultingCommand.Command, methodArgumentsList.ToArray(), eExecution);
                        }
                    }
                }
                else
                {
                    // Command not found, throw exception
                    throw new CommandNotFoundException(ResultingCommand.Command);
                }
            }
            catch (InvalidCommandArgumentsException e)
            {
                return(CommandExecutionResponse.Error(DefaultErrorStatus, e));
            }
            catch (CommandNotFoundException)
            {
                return(CommandExecutionResponse.NotFound(DefaultNotFoundStatus));
            }
            catch (Exception e)
            {
                return(CommandExecutionResponse.Error(DefaultErrorStatus, e));
            }
        }
예제 #2
0
        /// <summary>
        /// Register a type as a Command Class
        /// </summary>
        /// <param name="creationFunction">A <see cref="Func{T, TResult}"/> to define how the object is instantiated</param>
        /// <remarks>The type must implement the <see cref="CommandClassAttribute"/></remarks>
        /// <typeparam name="TCommandClassType">Type to be registered</typeparam>
        public void RegisterCommandClass <TCommandClassType>(Func <object> creationFunction)
        {
            // Command Class Type
            Type commandClassType = typeof(TCommandClassType);

            // Check if object implement the "CommandClassAttribute"
            if (!(commandClassType.GetCustomAttributes(true).Where(t => t is CommandClassAttribute)
                  .Select(t1 => t1)
                  .First() is CommandClassAttribute commandClassAttribute))
            {
                throw new Exception($"Class must implement the {nameof(CommandClassAttribute)}");
            }

            // Add registration information to the proper collection (make sure object is locked for thread-safety
            lock (CommandClassCollectionInternalLock)
            {
                if (CommandClassCollectionInternal.ContainsKey(commandClassType))
                {
                    // Command Class already registered
                    throw new Exception($"Command Class {commandClassType.Name} is already registered");
                }

                // Register the command class info
                var commandClassInfo = new CommandClassInfo()
                {
                    CommandClassAttribute = commandClassAttribute,
                    CommandClassType      = commandClassType,
                    CreationFunction      = creationFunction
                };

                CommandClassCollectionInternal.Add(commandClassType, commandClassInfo);
            }

            // Search Methods to find Commands
            foreach (var method in commandClassType.GetMethods())
            {
                // Loop thru all CommandAttribute's tied to the method (multiple names for the same command are acceptable)
                foreach (var commandAttribute in from methodAttribute
                         in method.GetCustomAttributes(true)
                         where methodAttribute is CommandAttribute
                         select methodAttribute as CommandAttribute)
                {
                    // Trigger the registering command event
                    var registeringCommandEventArgs = new RegisteringCommandEventArgs(commandAttribute.Command);
                    RegisteringCommand?.Invoke(this, registeringCommandEventArgs);

                    // Make sure command is allowed to be registered
                    if (!registeringCommandEventArgs.SuppressRegistration)
                    {
                        lock (CommandCollectionInternalLock)
                        {
                            // Process Duplicated Commands
                            if (CommandCollectionInternal.ContainsKey(commandAttribute.Command))
                            {
                                // Call duplicated event to decide what to do
                                var duplicatedCommandEventArgs = new DuplicatedCommandEventArgs(commandAttribute.Command, DuplicatedCommandActions.Ignore);
                                RegisteringDuplicatedCommand?.Invoke(this, duplicatedCommandEventArgs);

                                if (duplicatedCommandEventArgs.Action == DuplicatedCommandActions.Error)
                                {
                                    // ERROR: Command is duplicated
                                    throw new Exception($"Command '{commandAttribute.Command}' is duplicated and cannot be registered");
                                }
                                else if (duplicatedCommandEventArgs.Action == DuplicatedCommandActions.Replace)
                                {
                                    // Remove current command and let registration continue
                                    CommandCollectionInternal.Remove(commandAttribute.Command);
                                }
                                else
                                {
                                    // Skip to the next entry on the foreach loop
                                    continue;
                                }
                            }

                            // Setup the necessary information to run a command later
                            var commandInfo = new CommandInfo()
                            {
                                CommandAttribute = commandAttribute,
                                CommandClassType = commandClassType,
                                MethodInfo       = method,
                                ParametersInfo   = method.GetParameters(),
                            };

                            // Initialize the ParameterInfo array into an empty array if needed
                            if (commandInfo.ParametersInfo == null)
                            {
                                commandInfo.ParametersInfo = new ParameterInfo[] { }
                            }
                            ;

                            // Include Command Help Attributes on the CommandInfo
                            foreach (var commandHelpAttribute in from methodAttribute
                                     in method.GetCustomAttributes(typeof(CommandHelpAttribute), true)
                                     select methodAttribute as CommandHelpAttribute)
                            {
                                if (String.IsNullOrEmpty(commandHelpAttribute.CultureName))
                                {
                                    commandInfo.CommandHelpAttributes.Add("DEFAULT", commandHelpAttribute);
                                }
                                else
                                {
                                    commandInfo.CommandHelpAttributes.Add(commandHelpAttribute.CultureName, commandHelpAttribute);
                                }
                            }

                            // Add to the collection
                            CommandCollectionInternal.Add(commandAttribute.Command, commandInfo);
                        }
                    }
                }
            }
        }