public ExecuteRoutineAsyncResult(object apiResponse, RoutineExecution execMetric, CommonReturnValue mayAccess, Dictionary <string, string> responseHeaders)
 {
     this.ApiResponse            = apiResponse;
     this.RoutineExecutionMetric = execMetric;
     this.MayAccess       = mayAccess;
     this.ResponseHeaders = responseHeaders;
 }
Exemple #2
0
 /// <summary>
 /// Create a routine definition that will only run once
 /// </summary>
 public RoutineDefinition(RoutineExecution execution, Action method) : this()
 {
     IntervalInMs = 0;
     Execution    = execution;
     Method       = method;
     RunOnce      = true;
 }
Exemple #3
0
 /// <summary>
 /// Create a routine definition. Set the interval to 0 if you want to execute it on every update/fixed update
 /// Set the runOnce to true if you want to run the routine only 1 time
 /// </summary>
 public RoutineDefinition(int intervalInMs, RoutineExecution execution, Action method) : this()
 {
     IntervalInMs = intervalInMs;
     Execution    = execution;
     Method       = method;
     MethodName   = method.Method.Name;
 }
Exemple #4
0
        /// <summary>
        /// Changes the routine execution interval on the fly
        /// </summary>
        protected void ChangeRoutineExecutionInterval(RoutineExecution execution, string routineName, int newIntervalInMs)
        {
            RoutineDefinition routine;

            switch (execution)
            {
            case RoutineExecution.FixedUpdate:
                routine = FixedUpdateRoutines.FirstOrDefault(r => r.Name == routineName);
                break;

            case RoutineExecution.Update:
                routine = UpdateRoutines.FirstOrDefault(r => r.Name == routineName);
                break;

            case RoutineExecution.LateUpdate:
                routine = LateUpdateRoutines.FirstOrDefault(r => r.Name == routineName);
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(execution), execution, null);
            }

            if (routine != null)
            {
                routine.IntervalInMs = newIntervalInMs;
            }
            else
            {
                LunaLog.LogError($"[LMP]: Routine {execution}/{routineName} not defined");
            }
        }
        //(object/*ApiResponse | IActionResult*/, RoutineExecution, CommonReturnValue)
        public static async Task <ExecuteRoutineAsyncResult> ExecuteRoutineAsync(ExecOptions execOptions,
                                                                                 Dictionary <string, string> requestHeaders,
                                                                                 string referer,
                                                                                 string remoteIpAddress,
                                                                                 string appTitle,
                                                                                 string appVersion)
        {
            string debugInfo = null;

            Project     project  = null;
            Application app      = null;
            Endpoint    endpoint = null;
            Dictionary <string, string> responseHeaders = null;

            List <ExecutionPlugin> pluginList = null;

            RoutineExecution routineExecutionMetric = null;

            responseHeaders = new Dictionary <string, string>();

            try
            {
                if (!ControllerHelper.GetProjectAndAppAndEndpoint(execOptions.project, execOptions.application, execOptions.endpoint, out project,
                                                                  out app, out endpoint, out var resp))
                {
                    return(new ExecuteRoutineAsyncResult(resp, null, null, responseHeaders));
                }

                routineExecutionMetric = new RoutineExecution(endpoint, execOptions.schema, execOptions.routine);

                RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric);

                //  debugInfo += $"[{execOptions.schema}].[{execOptions.routine}]";

                string jsDALApiKey = null;

                if (requestHeaders.ContainsKey("api-key"))
                {
                    jsDALApiKey = requestHeaders["api-key"];
                }

                // make sure the source domain/IP is allowed access
                var mayAccess = app.MayAccessDbSource(referer, jsDALApiKey);

                if (!mayAccess.IsSuccess)
                {
                    return(new ExecuteRoutineAsyncResult(null, null, mayAccess, responseHeaders));
                }

                Dictionary <string, dynamic> outputParameters;
                int commandTimeOutInSeconds = 60;

                // PLUGINS
                var pluginsInitMetric = routineExecutionMetric.BeginChildStage("Init plugins");

                pluginList = InitPlugins(app, execOptions.inputParameters, requestHeaders);

                pluginsInitMetric.End();

                ////////////////////
                // Auth stage
                ///////////////////
                { // ask all ExecPlugins to authenticate
                    foreach (var plugin in pluginList)
                    {
                        if (!plugin.IsAuthenticated(execOptions.schema, execOptions.routine, out var error))
                        {
                            responseHeaders.Add("Plugin-AuthFailed", plugin.Name);
                            return(new ExecuteRoutineAsyncResult(new UnauthorizedObjectResult(error), null, null, responseHeaders));
                        }
                    }
                }

                var execRoutineQueryMetric = routineExecutionMetric.BeginChildStage("execRoutineQuery");

                string dataCollectorEntryShortId = DataCollectorThread.Enqueue(endpoint, execOptions);

                ///////////////////
                // Database call
                ///////////////////

                OrmDAL.ExecutionResult executionResult = null;

                try
                {
                    executionResult = await OrmDAL.ExecRoutineQueryAsync(
                        execOptions.CancellationToken,
                        execOptions.type,
                        execOptions.schema,
                        execOptions.routine,
                        endpoint,
                        execOptions.inputParameters,
                        requestHeaders,
                        remoteIpAddress,
                        pluginList,
                        commandTimeOutInSeconds,
                        execRoutineQueryMetric,
                        responseHeaders
                        );

                    outputParameters = executionResult.OutputParameterDictionary;
                    responseHeaders  = executionResult.ResponseHeaders;

                    execRoutineQueryMetric.End();

                    ulong?rows = null;

                    if (executionResult?.RowsAffected.HasValue ?? false)
                    {
                        rows = (ulong)executionResult.RowsAffected.Value;
                    }

                    DataCollectorThread.End(dataCollectorEntryShortId, rowsAffected: rows,
                                            durationInMS: execRoutineQueryMetric.DurationInMS,
                                            bytesReceived: executionResult.BytesReceived,
                                            networkServerTimeMS: executionResult.NetworkServerTimeInMS);
                }
                catch (Exception execEx)
                {
                    DataCollectorThread.End(dataCollectorEntryShortId, ex: execEx);

                    //                     if (execOptions != null && endpoint != null)
                    //                     {
                    //                         var wrapEx = new Exception($"Failed to execute {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", execEx);

                    //                         // create a fake frame to include the exec detail - this way any logger logging the StackTrace will always include the relevant execution detail
                    //                         StackFrame sf = new($"{execOptions.type} {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", 0);

                    //                         StackTrace st = new(sf);

                    //                         //?wrapEx.SetStackTrace(st);

                    // var allFields = wrapEx.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

                    // var fffff =string.Join("\r\n", allFields.Select(f=>f.Name).ToArray());

                    //                         var fi = wrapEx.GetType().GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);

                    //                         fi.SetValue(wrapEx, $"{endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}");

                    //                         throw wrapEx;
                    //                     }
                    //                     else throw;
                    throw; // rethrow
                }

                var prepareResultsMetric = routineExecutionMetric.BeginChildStage("Prepare results");

                if (!string.IsNullOrEmpty(executionResult.userError))
                {
                    return(new ExecuteRoutineAsyncResult(ApiResponse.ExclamationModal(executionResult.userError), routineExecutionMetric, mayAccess, responseHeaders));
                }

                var retVal = (IDictionary <string, object>) new System.Dynamic.ExpandoObject();
                var ret    = ApiResponse.Payload(retVal);

                retVal.Add("OutputParms", outputParameters);

                if (outputParameters != null)
                { // TODO: Consider making this a plugin
                    var possibleUEParmNames = (new string[] { "usererrormsg", "usrerrmsg", "usererrormessage", "usererror", "usererrmsg" }).ToList();

                    var ueKey = outputParameters.Keys.FirstOrDefault(k => possibleUEParmNames.Contains(k.ToLower()));

                    // if a user error msg is defined.
                    if (!string.IsNullOrWhiteSpace(ueKey) && !string.IsNullOrWhiteSpace(outputParameters[ueKey]))
                    {
                        ret.Message = outputParameters[ueKey];
                        ret.Title   = "Action failed";
                        ret.Type    = ApiResponseType.ExclamationModal;
                    }
                }

                if (execOptions.type == ExecType.Query)
                {
                    if (executionResult.ReaderResults != null)
                    {
                        var keys = executionResult.ReaderResults.Keys.ToList();

                        for (var i = 0; i < keys.Count; i++)
                        {
                            retVal.Add(keys[i], executionResult.ReaderResults[keys[i]]);
                        }

                        retVal.Add("HasResultSets", keys.Count > 0);
                        retVal.Add("ResultSetKeys", keys.ToArray());
                    }
                    else
                    {
                        var dataSet        = executionResult.DataSet;
                        var dataContainers = dataSet.ToJsonDS();

                        var keys = dataContainers.Keys.ToList();

                        for (var i = 0; i < keys.Count; i++)
                        {
                            retVal.Add(keys[i], dataContainers[keys[i]]);
                        }

                        retVal.Add("HasResultSets", keys.Count > 0);
                        retVal.Add("ResultSetKeys", keys.ToArray());
                    }
                }
                else if (execOptions.type == ExecType.NonQuery)
                {
                    // nothing to do
                }
                else if (execOptions.type == ExecType.Scalar)
                {
                    if (executionResult.ScalarValue is DateTime)
                    {
                        var dt = (DateTime)executionResult.ScalarValue;

                        // convert to Javascript Date ticks
                        var ticks = dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;

                        ret = ApiResponseScalar.Payload(ticks, true);
                    }
                    else
                    {
                        ret = ApiResponse.Payload(executionResult.ScalarValue);
                    }
                }

                prepareResultsMetric.End();
                routineExecutionMetric.End(executionResult.RowsAffected ?? 0);

                // enqueue a second time as we now have an End date and rowsAffected
                RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric);

                return(new ExecuteRoutineAsyncResult(ret, routineExecutionMetric, mayAccess, responseHeaders));
            }
            catch (SqlException ex) when(execOptions.CancellationToken.IsCancellationRequested && ex.Number == 0 && ex.State == 0 && ex.Class == 11)
            {
                // if we ended up here with a SqlException and a Cancel has been requested, we are very likely here because of the exception "Operation cancelled by user."
                // since MS does not provide an easy way (like a specific error code) to detect this scenario we have to guess

                routineExecutionMetric?.Exception(ex);

                throw new OperationCancelledByUserException(ex);
            }
            catch (Exception ex)
            {
                routineExecutionMetric?.Exception(ex);

                Connection dbConn = null;

                if (endpoint != null)
                {
                    // TODO: Fix!
                    dbConn = endpoint.GetSqlConnection();

                    // if (debugInfo == null) debugInfo = "";

                    // if (dbConn != null)
                    // {
                    //     debugInfo = $"{ endpoint.Pedigree } - { dbConn.InitialCatalog } - { debugInfo }";
                    // }
                    // else
                    // {
                    //     debugInfo = $"{ endpoint.Pedigree } - (no connection) - { debugInfo }";
                    // }
                }

                var exceptionResponse = ApiResponse.ExecException(ex, execOptions, out var exceptionId, debugInfo, appTitle, appVersion);

                if (debugInfo == null)
                {
                    debugInfo = "";
                }
                debugInfo = exceptionId + " " + debugInfo;

                // TODO: Get Execution plugin list specifically
                if (pluginList != null)
                {
                    string externalRef;

                    if (dbConn != null)
                    {
                        using (var con = new SqlConnection(dbConn.ConnectionStringDecrypted))
                        {
                            try
                            {
                                string additionalInfo = debugInfo;

                                if (execOptions?.inputParameters != null)
                                {
                                    additionalInfo += " ";
                                    additionalInfo += string.Join(",", execOptions.inputParameters.Select(kv => $"{kv.Key}={kv.Value}").ToArray());
                                }

                                con.Open();
                                ProcessPluginExecutionExceptionHandlers(pluginList, con, ex, additionalInfo, appTitle, appVersion, out externalRef);
                                ((dynamic)exceptionResponse.Data).ExternalRef = externalRef;
                            }
                            catch (Exception e)
                            {
                                ExceptionLogger.LogException(e, "ProcessPluginExecutionExceptionHandlers", "jsdal-server");
                            }
                        }
                    } // else: TODO: Log fact that we dont have a proper connection string.. or should plugins handle that?
                }

                // return it as "200 (Ok)" because the exception has been handled
                return(new ExecuteRoutineAsyncResult(exceptionResponse, routineExecutionMetric, null, responseHeaders));
            }
        }
 public RealtimeInfo(RoutineExecution re)
 {
     this.createdEpoch     = DateTime.Now.ToEpochMS();
     this.RoutineExecution = re;
 }